Synopsis du projet

Travail demandé

Le but de ce travail est de mettre en oeuvre les méthodes vues dans le module 3 “R et statistiques” pour explorer le jeu de données de Pavokovic, et de rendre un rapport d’analyse au format R markdown.

Nous fournissons ci-dessous une trame avec les principales sections attendues. Certaines contiennent déjà du code. Vous devrez en compléter d’autres. Sentez-vous libres d’adapter cette trame ou d’y ajouter des analyses complémentaires si elles vous aident à interpréter vos résultats.

Remise du rapport

Date: le 10 mai 2021 minuit. Si vous anticipez un problème pour remettre le rapport à cette date contactez-nous aussi rapidement que possible pour que nous puissions prévoir une remise plus tardive.

  • Commencez par renommer le fichier Rmd en remplaçant Prenom-NOM par vos nom et prénom.

  • Le rapport est attendu en formats Rmd + HTML (en gardant l’option self_contained de l’en-tête activée).

  • Déposez les fichiers dans un sous-dossier de vote compte du cluster. Attention, veillez à respecter précisément cette structure de chemin car nous nous baserons dessus pour récupérer vos résultats.

    /shared/projects/dubii2021/[login]/m3-stat-R/mini-projet

Critères d’évaluation

  • Reproductibilité des analyses: nous tenterons de regénérer le rapport HTML à partir de votre Rmd, en partant de notre compte sur le serveur IFB.
  • Manipulation des objets R
  • Mobilisation des méthodes statistiques vues au cours
  • Pertinence des interprétations statistiques
  • Pertinence des interprétations biologiques
  • Clarté de la rédaction
  • Clarté des illustrations (figures et tableaux): graphismes, légendes …

Nous vous encourageons à assurer la lisibilpité de votre code (syntaxe, nommage des variables, commentaires de code)

Objectifs scientifiques

Nous partons du même jeu de données Fil Rouge de ce module issues de la publication Pavkovic, M., Pantano, L., Gerlach, C.V. et al. Multi omics analysis of fibrotic kidneys in two mouse models. Sci Data 6, 92 (2019). https://doi.org/10.1038/s41597-019-0095-5

Rappel sur les échantillons:

Deux modèles de fibrose rénale chez la souris sont étudiés:

  1. Le premier est un modèle de néphropathie réversible induite par l’acide folique (folic acid (FA)). Les souris ont été sacrifiées avant le traitement (day 0), puis à jour 1, 2, 7 et 14 days après une seule injection d’acide folique.

  2. Le second est un modèle irréversible induit chrirurgicalement (unilateral ureteral obstruction (UUO)). les souris ont été sacrifiées avant obstruction (day 0) et à 3, 7 et 14 jours après obstruction par ligation de l’uretère du rein gauche.

A partir de ces extraits de rein, l’ARN messager total et les petits ARNs ont été séquencés et les protéines caratérisées par spectrométrie de masse en tandem (TMT).

But scientifique: Dans le tutoriel sur les dataframes, vous avez travaillé sur les données de transcriptome du modèle UUO. Dans ce mini-projet, vous allez travailler sur les données du modèle FA afin d’identifier de regrouper les observations (échantillon) et les gènes selon des profils d’expression similaires.

Votre projet se décompose en 4 parties:

  1. statsitiques descriptives des données brutes
  2. normalisation des données : commandes fournies
  3. statistiques descriptives des données normalisées
  4. analyse de regroupement des données

1. Les données brutes

Chargement des données brutes

Vous n’avez rien à coder ici. Le code est fourni.

Le bloc suivant contient une fonction qui permet de télécharger un fichier dans l’espace de travail, sauf s’il est déjà présent. Nous l’utiliserons ensuite pour télécharger les données à analyser en évitant de refaire le transfert à chaque exécution de l’analyse.

#' @title Download a file only if it is not yet here
#' @author Jacques van Helden email{Jacques.van-Helden@@france-bioinformatique.fr}
#' @param url_base base of the URL, that will be prepended to the file name
#' @param file_name name of the file (should not contain any path)
#' @param local_folder path of a local folder where the file should be stored
#' @return the function returns the path of the local file, built from local_folder and file_name
#' @export©
download_only_once <- function(
  url_base, 
  file_name,
  local_folder) {

  ## Define the source URL  
  url <- file.path(url_base, file_name)
  message("Source URL\n\t",  url)

  ## Define the local file
  local_file <- file.path(local_folder, file_name)
  
  ## Create the local data folder if it does not exist
  dir.create(local_folder, showWarnings = FALSE, recursive = TRUE)
  
  ## Download the file ONLY if it is not already there
  if (!file.exists(local_file)) {
    message("Downloading file from source URL to local file\n\t", 
            local_file)
    download.file(url = url, destfile = local_file)
  } else {
    message("Local file already exists, no need to download\n\t", 
            local_file)
  }
  
  return(local_file)
}

Nous téléchargeons deux fichiers dans un dossier local ~/m3-stat-R/pavkovic_analysis (vous pouvez changer le nom ou chemin dans le chunk ci-dessous), et les chargeons dans les data.frames suivants:

  • Données brutes de transcriptome: fa_expr_raw
  • Métadonnées: fa_meta
## Define the remote URL and local folder
pavkovic_url <- "https://github.com/DU-Bii/module-3-Stat-R/raw/master/stat-R_2021/data/pavkovic_2019/"

## Define the local folder for this analysis (where the data will be downloaded and the results generated)
pavkovic_folder <- "~/m3-stat-R/pavkovic_analysis"

## Define a sub-folder for the data
pavkovic_data_folder <- file.path(pavkovic_folder, "data")

## Download and load the expression data table
## Note: we use check.names=FALSE to avoid replacing hyphens by dots
## in sample names, because we want to keep them as in the 
## original data files. 
message("Downloading FA transcriptome file\t", "fa_raw_counts.tsv.gz",
  "\n\tfrom\t", pavkovic_url)
fa_expr_file <- download_only_once(
  url_base = pavkovic_url, 
  file_name = "fa_raw_counts.tsv.gz",
  local_folder = pavkovic_data_folder)

## Load the expresdsion table
message("Loading FA transcriptome data from\n\t", fa_expr_file)
fa_expr_raw <- read.delim(file = fa_expr_file, 
                       header = TRUE, 
                       row.names = 1)

## Download the metadata file
message("Downloading FA metadata file\t", "fa_transcriptome_metadata.tsv",
  "\n\tfrom\t", pavkovic_url)
fa_meta_file <- download_only_once(
  url_base = pavkovic_url, 
  file_name = "fa_transcriptome_metadata.tsv",
  local_folder = pavkovic_data_folder)

## Load the metadata
message("Loading FA metadata from\n\t", fa_meta_file)
fa_meta <- read.delim(file = fa_meta_file, 
                       header = TRUE, 
                       row.names = 1)

Nous regardons la structure de chaque dataframe.

str(fa_expr_raw)
'data.frame':   46679 obs. of  18 variables:
 $ day1_1  : num  2278.8 0 36.3 13.2 0 ...
 $ day1_2  : num  1786.5 0 22.15 7.15 27.9 ...
 $ day1_3  : num  2368.62 0 39.48 1.12 6.9 ...
 $ day14_1 : num  627.758 0 14.471 0.867 5.692 ...
 $ day14_2 : num  559.2 0 10.2 0 1.9 ...
 $ day14_3 : num  611.434 0 31.691 0 0.655 ...
 $ day2_1  : num  2145.22 0 300.56 1.71 57.38 ...
 $ day2_2  : num  262.45 0 4.77 0 0 ...
 $ day2_3  : num  745.84 0 123.9 5.26 38.9 ...
 $ day3_1  : num  987.185 0 51.856 0.802 8.931 ...
 $ day3_2  : num  1077.65 0 8.43 0 6.97 ...
 $ day3_3  : num  1335.1 0 69.9 0 0 ...
 $ day7_1  : num  1096.08 0 6.67 0 7.94 ...
 $ day7_2  : num  1035.846 0 6.955 0.849 101.648 ...
 $ day7_3  : num  1090.04 0 42.58 1.71 0.65 ...
 $ normal_1: num  483.23 0 7.35 0.86 32.06 ...
 $ normal_2: num  1842.1 0 11.2 0 10.4 ...
 $ normal_3: num  475.7 0 1.03 0 0 ...
str(fa_meta)
'data.frame':   18 obs. of  5 variables:
 $ dataType    : chr  "transcriptome" "transcriptome" "transcriptome" "transcriptome" ...
 $ sampleName  : chr  "day14_1" "day14_2" "day14_3" "day1_1" ...
 $ condition   : chr  "day14" "day14" "day14" "day1" ...
 $ sampleNumber: int  1 2 3 1 2 3 1 2 3 1 ...
 $ color       : chr  "#FF4400" "#FF4400" "#FF4400" "#BBD7FF" ...

Les deux fichiers ne donnent pas les observations de l’échantillon dans le même ordre:

fa_meta$sampleName == names(fa_expr_raw)
 [1] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE

Nous les réorganisons les échantillons dans l’ordre de l’expérience: condition normale, puis day 1 à 14 avec les 3 réplicats.

sample_order <- c(paste0("normal_", 1:3), paste0("day1_", 1:3), paste0("day2_", 1:3), paste0("day3_", 1:3), paste0("day7_", 1:3), paste0("day14_", 1:3))

fa_expr_raw <- fa_expr_raw[,sample_order]
fa_meta <- fa_meta[match(sample_order, fa_meta$sampleName),]

# View(fa_meta)
kable(fa_meta, caption = "Metdata for Pavkovoc FA transcriptome")
Metdata for Pavkovoc FA transcriptome
dataType sampleName condition sampleNumber color
16 transcriptome normal_1 normal 1 #BBFFBB
17 transcriptome normal_2 normal 2 #BBFFBB
18 transcriptome normal_3 normal 3 #BBFFBB
4 transcriptome day1_1 day1 1 #BBD7FF
5 transcriptome day1_2 day1 2 #BBD7FF
6 transcriptome day1_3 day1 3 #BBD7FF
7 transcriptome day2_1 day2 1 #F0BBFF
8 transcriptome day2_2 day2 2 #F0BBFF
9 transcriptome day2_3 day2 3 #F0BBFF
10 transcriptome day3_1 day3 1 #FFFFDD
11 transcriptome day3_2 day3 2 #FFFFDD
12 transcriptome day3_3 day3 3 #FFFFDD
13 transcriptome day7_1 day7 1 #FFDD88
14 transcriptome day7_2 day7 2 #FFDD88
15 transcriptome day7_3 day7 3 #FFDD88
1 transcriptome day14_1 day14 1 #FF4400
2 transcriptome day14_2 day14 2 #FF4400
3 transcriptome day14_3 day14 3 #FF4400

=> Ainsi, nous avons un jeu de données avec un échantillon de 18 observations et des données d’expression de 46679 gènes.

Transformation log2

A vous de jouer!

Appliquez une transformation log2 des données brutes, après avoir ajouté un epsilon \(\epsilon = 1\) (les valeurs nulles seront donc représentées par un log2(counts) valant \(0\). Stockez le résultat dans un data.frame nommé fa_expr_log2.

Affchez un fragment des tableaux fa_expr_raw et fa_expr_log2 en sélectionnant les lignes 100 à 109 et les colonnes 5 à 10, afin de vous assurer que la transformation log2 a bien fonctionné.

## Log2 transformation of the transcriptome data
epsilon <- 1
fa_expr_log2 <- log2(fa_expr_raw + epsilon)
# dim(fa_expr_log2)
# View(head(fa_expr_log2))

## Display of a fragment of the data before and after log2 transformation
kable(fa_expr_raw[100:109, 5:10], caption = "Fragment des données transcriptomiques brutes")
Fragment des données transcriptomiques brutes
day1_2 day1_3 day2_1 day2_2 day2_3 day3_1
ENSMUSG00000000567 599.648075 304.093177 1052.689447 106.995584 347.13842 479.59911
ENSMUSG00000000568 1008.026306 1349.126716 818.257157 116.136417 3406.45030 766.09722
ENSMUSG00000000579 599.832586 500.735597 473.399472 36.258005 410.57787 347.05054
ENSMUSG00000000581 942.511039 744.646735 546.260344 87.788535 319.12679 461.45732
ENSMUSG00000000594 3063.845705 2743.404374 2283.051270 1115.588250 1491.61384 1576.68451
ENSMUSG00000000600 3341.854530 3561.805180 3674.188108 589.840068 1399.10751 3446.01856
ENSMUSG00000000605 791.312561 558.710943 489.579657 77.008213 256.00200 282.80077
ENSMUSG00000000606 4.785252 6.566374 3.970399 0.000000 138.48677 0.00000
ENSMUSG00000000617 15.839486 29.138902 30.228506 6.670500 18.27493 13.41152
ENSMUSG00000000627 36.673777 35.967747 46.297517 2.878275 17.03638 15.45854
kable(fa_expr_log2[100:109, 5:10], caption = "Fragment des données transcriptomiques après transformation log2")
Fragment des données transcriptomiques après transformation log2
day1_2 day1_3 day2_1 day2_2 day2_3 day3_1
ENSMUSG00000000567 9.230376 8.253106 10.041234 6.754829 8.443517 8.908690
ENSMUSG00000000568 9.978748 10.398879 9.678173 6.872046 11.734477 9.583266
ENSMUSG00000000579 9.230819 8.970783 8.889959 5.219479 8.685022 8.443153
ENSMUSG00000000581 9.881896 9.542348 9.096084 6.472302 8.322500 8.853176
ENSMUSG00000000594 11.581599 11.422277 11.157379 10.124882 10.543625 10.623593
ENSMUSG00000000600 11.706865 11.798798 11.843602 9.206624 10.451322 11.751133
ENSMUSG00000000605 9.629926 9.128538 8.938344 6.285554 8.005636 8.148735
ENSMUSG00000000606 2.532380 2.919602 2.313362 0.000000 7.123984 0.000000
ENSMUSG00000000617 4.073776 4.913555 4.964792 2.939321 4.268654 3.849151
ENSMUSG00000000627 5.235489 5.208195 5.563693 1.955415 4.172838 4.040765

Statistiques descriptives

A vous de jouer!

Dans le tutorial sur les dataframes sur le jeu de données “uuo” (relisez le corrigé), nous vous avons demandé de créer un data.frame qui collectera les statistiques par gène et par échantillon. Nous vous demandons de réaliser une étude similaire sur les données “FA”.

Par échantillon avant normalisation

Nous créons un data.frame nommé sample_stat_prenorm qui comportera une ligne par échantillon et une colonne par statistique, et calculez les statistiques suivantes sur les valeurs log2 d’expression de chaque échantillon:

  • moyenne
  • écart-type
  • intervalle inter-quartiles
  • premier quartile
  • médiane
  • troisième quartile
  • maximum
  • nombre de valeurs nulles

Il sera affiché avec la fonction kable() (n’oubliez pas la légende).

sample_stat_prenorm <- data.frame(
  mean = apply(fa_expr_log2, 2, mean, na.rm=TRUE),
  sd = apply(fa_expr_log2, 2, sd, na.rm=TRUE),
  iqr = apply(fa_expr_log2, 2, IQR, na.rm=TRUE),
  Q1 = apply(fa_expr_log2, 2, quantile, p = 0.25, na.rm=TRUE),
  median = apply(fa_expr_log2, 2, median, na.rm=TRUE),
  Q3 = apply(fa_expr_log2, 2, quantile, p = 0.75, na.rm=TRUE),
  max = apply(fa_expr_log2, 2, max, na.rm=TRUE),
  null = apply(fa_expr_log2 == 0, 2, sum, na.rm=TRUE)
)

kable(sample_stat_prenorm, caption = "Sample-wise statistics before normalisation.")
Sample-wise statistics before normalisation.
mean sd iqr Q1 median Q3 max null
normal_1 2.876096 3.382687 5.566333 0 1.115495 5.566333 23.48952 21415
normal_2 3.475984 3.851531 6.702346 0 1.991260 6.702346 24.43355 20203
normal_3 2.796962 3.301372 5.419783 0 1.057748 5.419783 23.47218 21660
day1_1 3.003791 3.526143 5.901203 0 1.074190 5.901203 23.65130 21700
day1_2 3.516314 3.877108 6.816493 0 2.019190 6.816493 24.58225 20152
day1_3 3.607632 3.917601 6.946987 0 2.252981 6.946987 24.70005 19621
day2_1 3.608242 3.944769 7.000535 0 2.170400 7.000535 24.32636 19978
day2_2 2.088503 2.832614 3.958139 0 0.000000 3.958139 21.94520 24446
day2_3 3.071767 3.573520 6.008117 0 1.309292 6.008117 23.47538 21455
day3_1 3.233264 3.638371 6.300924 0 1.621087 6.300924 23.41316 20772
day3_2 3.163046 3.595828 6.231985 0 1.556653 6.231985 23.03797 21142
day3_3 3.440775 3.777905 6.727257 0 1.991235 6.727257 23.97460 20287
day7_1 3.296409 3.667439 6.515860 0 1.703173 6.515860 23.34983 20865
day7_2 3.251819 3.587848 6.350051 0 1.822801 6.350051 23.20695 20467
day7_3 3.289691 3.617495 6.400883 0 1.954087 6.400883 23.53623 20045
day14_1 3.102341 3.484017 6.044672 0 1.597272 6.044672 23.41418 20496
day14_2 3.147517 3.557991 6.130466 0 1.600695 6.130466 23.87931 20870
day14_3 3.192445 3.533419 6.250143 0 1.699228 6.250143 23.46460 20396

Par gène avant normalisation

Nous crésons ci-dessous un data.frame nommé gene_stat_prenorm qui comportera une ligne par gène et une colonne par statistique, et calculez les statistiques suivantes sur les valeurs log2 de chaque gène.

  • moyenne
  • médiane
  • écart-type
  • premier quartile
  • troisième quartile
  • maximum
  • nombre de valeurs nulles
  • intervalle inter-quartiles

Ces résultats seront stockés dans un data.frame avec 1 ligne par échantillon et 1 colonne par statistique. Vous afficherez les lignes 100 à 109 de ce tableau de statistiques avec la fonction kable() (n’oubliez pas la légende).

## Gene-wise statistics before normalisation
gene_stat_prenorm <- data.frame(
  mean = apply(fa_expr_raw, 1, mean, na.rm=TRUE),
  sd = apply(fa_expr_raw, 1, sd, na.rm=TRUE),
  iqr = apply(fa_expr_raw, 1, IQR, na.rm=TRUE),
  Q1 = apply(fa_expr_raw, 1, quantile, p = 0.25, na.rm=TRUE),
  median = apply(fa_expr_raw, 1, median, na.rm=TRUE),
  Q3 = apply(fa_expr_raw, 1, quantile, p = 0.75, na.rm=TRUE),
  max = apply(fa_expr_raw, 1, max, na.rm=TRUE),
  null = apply(fa_expr_raw == 0, 1, sum, na.rm=TRUE)
)

kable(gene_stat_prenorm[100:109, ], caption = "Gene-wise statistics before normalisation.")
Gene-wise statistics before normalisation.
mean sd iqr Q1 median Q3 max null
ENSMUSG00000000567 320.336744 286.399084 364.362298 91.046357 261.659973 455.40866 1052.68945 0
ENSMUSG00000000568 857.348825 694.432189 282.978949 575.571199 711.096105 858.55015 3406.45030 0
ENSMUSG00000000579 422.075702 162.290415 157.732606 340.417017 447.572742 498.14962 728.51737 0
ENSMUSG00000000581 498.978227 248.612029 259.324338 320.599085 471.269224 579.92342 942.51104 0
ENSMUSG00000000594 2113.558334 1237.521694 937.571105 1400.226182 1868.718762 2337.79729 6408.96044 0
ENSMUSG00000000600 2299.978163 911.222891 1480.192582 1719.874980 2093.572929 3200.06756 3674.18811 0
ENSMUSG00000000605 325.552118 169.456624 195.879561 221.735904 282.608205 417.61546 791.31256 0
ENSMUSG00000000606 9.929258 32.151350 2.736779 1.178311 1.934744 3.91509 138.48677 4
ENSMUSG00000000617 18.804116 9.470819 13.654701 12.188276 18.150391 25.84298 36.00235 0
ENSMUSG00000000627 15.623306 14.341852 12.308127 4.780913 10.064895 17.08904 46.29752 0

2. Filtrage et normalisation des données

Nous fournissons ci-dessous le code pour normaliser les données, en standardisant le 3ème quantile.

La méthode choisie ici consiste à

  • écarter les gènes “non-détectés”, c’est -à-dire ceux ayant des valeurs nulles dans au moins 90% des échantillons;

  • écarter les gènes à peine exprimés, c’est-à-dire ceux ayant une valeur moyenne < 10 (arbitrairement);

  • standardiser les échantillons sur le 3ème quartile des valeurs non-nulles. Nous vous rappelons que d’autres méthodes plus élaborées ont été vues dans les modules 4 et 5, mais nous avons choisi ici une solution simple tout en étant robuste.

Filtrage 1 : élimination des gènes non détectés ou à peine exprimés

## Data filtering: genes having at least 90% null values
undetected_genes <- gene_stat_prenorm$null >= ncol(fa_expr_raw) * 0.9
print(paste0("Undetected genes (null in >= 90% samples): ", sum(undetected_genes)))
[1] "Undetected genes (null in >= 90% samples): 14380"
## Data filtering: genes having a mean expression < 10
barely_expressed_genes <- gene_stat_prenorm$mean < 10
print(paste0("Barely expressed genes (mean < 10): ", sum(barely_expressed_genes)))
[1] "Barely expressed genes (mean < 10): 26286"
## Apply filtering on both criteria
discarded_genes <- undetected_genes | barely_expressed_genes
print(paste0("Discarded genes: ", sum(discarded_genes)))
[1] "Discarded genes: 26288"
kept_genes <- !discarded_genes
print(paste0("Kept genes: ", sum(kept_genes)))
[1] "Kept genes: 20391"
## Genes after filtering
fa_expr_log2_filtered <- fa_expr_log2[kept_genes, ]

Normalisation entre échantillons

Vous n’avez rien à coder ici. Le code est fourni.

## Generate a data frame where null values are replaced by NA
fa_expr_nonull <- fa_expr_log2_filtered
fa_expr_nonull[fa_expr_log2_filtered <= 0] <- NA
sum(is.na(fa_expr_nonull))
[1] 5598
## Compute the 3rd quartile of non-null values for each sample
sample_q3_nonull <- apply(fa_expr_nonull, 2, quantile, prob = 0.75, na.rm = TRUE)
# print(sample_q3_nonull)

## Compute the A3 for all the values, which will serve as target value for the standardised sample Q3
all_q3_nonull <- quantile(unlist(fa_expr_nonull), prob = 0.75, na.rm = TRUE)
# print(all_q3_nonull)

## Standardise expression on  the third quartile of non-null values
## Beware : for this standardization we keep the null values
##
## Trick : I transpose the table to apply the ratio sample per sample, 
## and then transpose the result to get the genes in rows and samples in columns
fa_expr_log2_standard <- t(t(fa_expr_log2_filtered) / sample_q3_nonull * all_q3_nonull)
# quantile(unlist(fa_expr_log2_standard), probs = 0.75, na.rm = TRUE)

## I also compute the values for the "nonull" table for 
## the sake of comparison and to check that the third quantiles of non-null 
## values are well identical across samples.
fa_expr_log2_standard_nonull <- t(t(fa_expr_nonull) / sample_q3_nonull * all_q3_nonull)
# quantile(unlist(fa_expr_log2_standard_nonull), probs = 0.75, na.rm = TRUE)

## Compute 3 before and after standardisation, including or not the null values
standardisation_impact <- data.frame(
  before_all = apply(fa_expr_log2_filtered, 2, quantile, prob =  0.75, na.rm = TRUE),
  before_nonull = apply(fa_expr_nonull, 2, quantile, prob =  0.75, na.rm = TRUE),
  after_nonul = apply(fa_expr_log2_standard_nonull, 2, quantile, prob =  0.75, na.rm = TRUE),
  after_all = apply(fa_expr_log2_standard, 2, quantile, prob =  0.75, na.rm = TRUE)
)

## Note: after standardization the Q3 of the data we will use show some variations 
## because we compute them here with the null values
kable(standardisation_impact, caption = "Impact of standardization on the third quantile (Q3) per sample. Third quantiles are computed before and after standardisation, with either all the values of the filtered table, or only the non-null values. ")
Impact of standardization on the third quantile (Q3) per sample. Third quantiles are computed before and after standardisation, with either all the values of the filtered table, or only the non-null values.
before_all before_nonull after_nonul after_all
normal_1 7.892971 7.929471 8.546124 8.506785
normal_2 9.095259 9.120100 8.546124 8.522846
normal_3 7.690496 7.728764 8.546124 8.503808
day1_1 8.275875 8.310843 8.546124 8.510165
day1_2 9.215737 9.236819 8.546124 8.526618
day1_3 9.339495 9.356787 8.546124 8.530330
day2_1 9.372222 9.391459 8.546124 8.528618
day2_2 6.401122 6.563217 8.546124 8.335056
day2_3 8.428494 8.462042 8.546124 8.512242
day3_1 8.600914 8.618327 8.546124 8.528857
day3_2 8.449052 8.476090 8.546124 8.518862
day3_3 8.929730 8.945829 8.546124 8.530744
day7_1 8.637420 8.652095 8.546124 8.531628
day7_2 8.457495 8.478531 8.546124 8.524920
day7_3 8.568555 8.585032 8.546124 8.529721
day14_1 8.181662 8.194184 8.546124 8.533064
day14_2 8.339087 8.364105 8.546124 8.520562
day14_3 8.313689 8.334105 8.546124 8.525188

3. Les données normalisées

Statistiques par gène après normalisation, et annotation des gènes

Générez un data.frame avec une ligne par gène à partir du tableau de données normalisées, avec les statistiques suivantes (une statistique par colonne):

  • moyenne
  • variance
  • écart-type
  • coefficient de variation
  • minimum
  • médiane
  • maximum

Chaque gène étant donné par son identifiant dans la base de données ENSEMBL vous utiliserez le paquet biomaRt pour ajouter des annotations à chacun des gènes: symbole, chromosome, coordonnées génomiques.

Ajoutez une colonne avec les annotations biomart de chaque gène.

#### Gene annotations ####

Affichez les lignes 100 à 109 de ce tableau de statistiques.

#### Stat per gene after normalisation ####

## Gene-wise statistics before normalisation
gene_stat_norm <- data.frame(
  mean = apply(fa_expr_log2_standard, 1, mean),
  var = apply(fa_expr_log2_standard, 1, var),
  sd = apply(fa_expr_log2_standard, 1, sd))
gene_stat_norm$V = gene_stat_norm$sd / gene_stat_norm$mean
gene_stat_norm$min = apply(fa_expr_log2_standard, 1, min)
gene_stat_norm$median = apply(fa_expr_log2_standard, 1, median)
gene_stat_norm$max = apply(fa_expr_log2_standard, 1, max)

kable(gene_stat_norm[100:109, ], caption = "Gene-wise statistics after normalisation.")
Gene-wise statistics after normalisation.
mean var sd V min median max
ENSMUSG00000000711 9.938709 0.8858583 0.9412004 0.0947005 9.232061 9.727752 13.292801
ENSMUSG00000000724 5.595006 0.1972841 0.4441668 0.0793863 4.816940 5.632564 6.426584
ENSMUSG00000000731 4.936591 0.5868326 0.7660500 0.1551779 3.715427 4.984400 6.247104
ENSMUSG00000000732 5.543710 2.5859031 1.6080743 0.2900719 2.534509 5.736471 8.250127
ENSMUSG00000000738 8.426883 0.2746445 0.5240653 0.0621897 7.477862 8.330412 9.484120
ENSMUSG00000000740 10.820838 0.1330747 0.3647941 0.0337122 10.156481 10.850259 11.622910
ENSMUSG00000000743 7.619693 1.1794074 1.0860052 0.1425261 6.110121 7.398740 10.866373
ENSMUSG00000000751 7.368976 0.5226199 0.7229245 0.0981038 5.984878 7.241989 9.010795
ENSMUSG00000000753 2.830650 2.9689424 1.7230619 0.6087160 0.000000 3.131414 5.177214
ENSMUSG00000000759 8.617902 0.3027336 0.5502123 0.0638453 7.904374 8.497898 10.039692

Distribution des données

  • Dessinez sous forme d’un histogramme la distribution des valeurs après normalisation (tous échantillons confondus)
hist(unlist(fa_expr_log2_standard), 
     breaks = seq(from = 0, to = max(fa_expr_log2_standard) + 1, by = 0.25),
     xlab = "log2(counts) after standardisation", 
     ylab = "number of genes after filtering",
     col = "#BBDDFF",
     las = 1, cex.axis = 0.8,
     main = "distribution after standardisation")
abline(v = mean(fa_expr_log2_standard), col = "darkgreen", lwd = 2)
Distribution of expression values (log2 counts) after gene filtering and standardisation on the sample-wise third-quartile of non-null values. The vertical line highlights the mean value.

Distribution of expression values (log2 counts) after gene filtering and standardisation on the sample-wise third-quartile of non-null values. The vertical line highlights the mean value.

  • Dessinez un box plot des échantillons avant et après normalisation, et commentez la façon dont l’effet de la normalisation apparaît sur ces graphiques.
#### Box plots to show normalisation impact ####
par(mar = c(4,6,4,1)) ## Set the margins
par(mfrow = c(2,2))
boxplot(fa_expr_log2_filtered, 
        horizontal = TRUE,
        xlab = "log2(counts)", 
        las = 1, 
        col = fa_meta$color, 
        main = "Before standardisation\nall values")
boxplot(fa_expr_nonull, 
        horizontal = TRUE, 
        xlab = "log2(counts)", 
        las = 1, 
        col = fa_meta$color, 
        main = "Before standardisation\nzeros discarded")
boxplot(fa_expr_log2_standard_nonull, 
        horizontal = TRUE, 
        xlab = "log2(counts)", 
        las = 1, 
        col = fa_meta$color, 
        main = "Standardised\nzeros discarded")
boxplot(fa_expr_log2_standard, 
        xlab = "log2(counts)", 
        las = 1, 
        horizontal = TRUE, 
        col = fa_meta$color, 
        main = "Standardised\nall values")
Box plots showing the impact of normalisation

Box plots showing the impact of normalisation

par(mfrow = c(1, 1))
par(mar = c(4,5,5,1))

4. Analyse de regroupement des données

Filtrage 2 : sélection de gènes d’expression élevée et variable

Pour réduire le nombre de gènes, nous allons écarter les gènes faiblement exprimés (log2 moyen inférieur à 4), et ne retenir que ceux qui montrent des variations importantes entre échantillons. Pour ce dernier critère , nous nous basons sur le coefficient de variation, afin de relativiser la dispersion (écart type) par rapport à la tendance centrale (moyenne).

Sélectionnez les gènes ayant un niveau log2 moyen minimal supérieur à 3 (\(s > 3\)) et un coefficient de variation supérieur à 0.5 (\(V > 0.5\)). Note: ces valeurs sont parfaitement arbitraires, elles ont érté choisies pour obtenir un nombre raisonnable de gènes.

#### Gene selection ####

## Compute a Boolean vector indicating whether each gene passes or not the expression level threshold
high_expression <- gene_stat_norm$mean > 3
# table(high_expression) # count number of genes with high/weak expression

## Compute a Boolean vector indicating whether each gene passes or not the variation coefficient threshold
high_variation <- gene_stat_norm$V > 0.5
# table(high_variation) # count number of genes with weak high coeffficient of variation

## Compute a Boolean vector indicating whether each gene passes or not the variance threshold
# high_variance <- gene_stat_norm$var > 2
# table(high_variance) # count number of genes with weak high variance

## Select genes having both a high mean expression and a high variation coefficien
selected_genes <- high_variation & high_expression
# table(selected_genes) # count number of genes with weak high coeffficient of variation
print(paste0("Selected genes: ", sum(selected_genes)))
[1] "Selected genes: 501"
## Create a data frame with the expression of the selected genes
fa_expr_selected <- fa_expr_log2_standard[selected_genes, ]

Dessinez des histogrammes des valeurs d’expression avant et après cette sélection de gènes, et commentez les différences.

#### Histograms of expression before and after gene selection ####

par(mfrow = c(2,1))
hist(unlist(fa_expr_log2_standard), 
     breaks = seq(from = 0, to = max(fa_expr_log2_standard) + 1, by = 0.25),
     main = "Standardized values before gene selecetion",
     col =  "#DDBBFF")

hist(unlist(fa_expr_selected), 
     breaks = seq(from = 0, to = max(fa_expr_log2_standard) + 1, by = 0.25),
     main = "Standardized values after gene selecetion",
     col =  "#FFDDBB")
Distribution of expression values before and after gene selection

Distribution of expression values before and after gene selection

par(mfrow = c(1,1))


# ## Some quick checks: the selection of highly variable genes select those having many zeros - and high values in other samples
# hist(unlist(fa_expr_log2_filtered[high_expression, ]), breaks=100)
# hist(unlist(fa_expr_log2_filtered[high_variation, ]), breaks=100)
# hist(unlist(fa_expr_log2_filtered[!high_variation, ]), breaks=100)
# hist(unlist(fa_expr_log2_filtered[selected_genes, ]), breaks=100)

Dessinzes un box plot des valeurs d’expression avant et après sélection des gènes, et commentez le résultat.

#### Histogram of expression after gene selection ####

par(mfrow = c(1,2))

boxplot(fa_expr_log2_standard, 
        horizontal = TRUE,
        xlab = "log2(counts)", 
        las = 1, 
        col = fa_meta$color, 
        main = "Before gene selection\nstandardised values")
boxplot(fa_expr_selected, 
        horizontal = TRUE,
        xlab = "log2(counts)", 
        las = 1, 
        col = fa_meta$color, 
        main = "After gene selection\nstandardised values")
Box plots of standardised expression values before and after gene selection.

Box plots of standardised expression values before and after gene selection.

par(mfrow = c(1,1))

ACP

Dessinez un plot ACP des échantillons en les colorant par condition.

  • avec la matrice d’expression initiale (\(fa_expr\))
# tous les gènes
ma_pca_raw_tt <- PCA(t(fa_expr_raw), 
                      scale.unit = FALSE, 
                      graph = FALSE)
plot(ma_pca_raw_tt, choix = "ind")
PC plot of the samples from the raw expression values.

PC plot of the samples from the raw expression values.

fviz_pca_ind(ma_pca_raw_tt, col.ind = fa_meta[, "color"])
PC plot of the samples from the raw expression values.

PC plot of the samples from the raw expression values.

# seulement les gènes sélectionnés
ma_pca_raw_sel <- PCA(t(fa_expr_raw[selected_genes,]), 
                      scale.unit = FALSE, 
                      graph = FALSE)
fviz_pca_ind(ma_pca_raw_sel, col.ind = fa_meta[, "color"])
PC plot of the samples from the raw expression values.

PC plot of the samples from the raw expression values.

  • avec la matrice finale (transformation log2, filtre des gènes non-détectés, standardisation et sélection des gènes fortement exprimés et a-à haut coefficient de variation)
ma_pca_sel <- PCA(t(fa_expr_selected), scale.unit = FALSE,
                  graph = FALSE)
plot(ma_pca_sel, choix = "var")
PC plot of the samples from standardised values after gene selection.

PC plot of the samples from standardised values after gene selection.

plot(ma_pca_sel, choix = "ind")
PC plot of the samples from standardised values after gene selection.

PC plot of the samples from standardised values after gene selection.

fviz_pca_ind(ma_pca_sel, col.ind = fa_meta[, "color"])
PC plot of the samples from standardised values after gene selection.

PC plot of the samples from standardised values after gene selection.

Clustering

  • Calculez les matrices de distance entre échantillons, en utilisant respectivement les distances euclidienne (dist()), coefficient de Pearson (cor(, method = "pearson")) et de Spearman (cor(, method = "spearman")).
dist_euc_sel <- dist(t(fa_expr_selected))
cor_pearson_sel <- as.dist(1 - cor(fa_expr_selected))
cor_spearman_sel <- 1 - as.dist(cor(fa_expr_selected, method = "spearman"))
  • Effectuez un clustering hiérarchique des échantillons, en utilisant le critère de Ward (ward.d2) pour l’agglomération. Comparez les arbres d’échantillons obtenus avec ces trois métriques et choisissez celle qui vous paraît la plus pertinente.
par(mfrow = c(1,3))
plot(hclust(dist_euc_sel), hang = -1,
     main = "euclidean distance")
plot(hclust(cor_pearson_sel), hang = -1,
     main = "pearson")
plot(hclust(cor_spearman_sel), hang = -1,
     main = "spearman")

par(mfrow = c(1,1))
  • Effectuez un clustering hiérarchique des gènes en utilisant la distance basée sur le coefficient de Pearson et le critère de Ward
cor_pearson_gene <- as.dist(1 - cor(t(fa_expr_selected)))
plot(hclust(cor_pearson_gene), hang = -1,
     main = "gènes")

  • Dessinez un arbre avec le résultat du clustering des gènes et commentez sa strcuture. Si vous deviez choisir de façon arbitraire un nombre de clusters, que choisiriez-vous ? Pourquoi ? Pas de panique, nous pouvons assumer ici que la réponse comporte une part de subjectivité.
plot(hclust(cor_pearson_gene), hang = -1,
     main = "gènes")
rect.hclust(hclust(cor_pearson_gene), k = 7)

  • Dessinez une heatmap du résultat, en sélectionnant les deux résultats de clustering ci-dessus pour les gènes et les échantillons.
pheatmap(t(fa_expr_selected),
         clustering_distance_cols = "correlation", clustering_distance_rows = "correlation")

Interprétez les résultats en quelques phrases.

Enrichissement fonctionnel

Effectuez une analyse d’enrichissement fonctionnel avec les principaux clusters obtenus dans la section précédente.

Conclusions générales

Résumez en quelques phrases vos conclusions à partir des résultats obtenus.

LS0tCnRpdGxlOiAiTWluaS1wcm9qZXQgMjAyMSAtIEV4cGxvcmF0aW9uIGRlcyBkb25uw6llcyBkZSBQYXZrb3ZpYyIKYXV0aG9yOiAiUHLDqW5vbSBOb20iCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBzZWxmX2NvbnRhaW5lZDogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4KICAgIHRoZW1lOiBjZXJ1bGVhbgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogImhpZGUiCiAgcGRmX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIHNldHRpbmdzLCBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFLCBldmFsPVRSVUV9CgpvcHRpb25zKHdpZHRoID0gMzAwKQojIG9wdGlvbnMoZW5jb2RpbmcgPSAnVVRGLTgnKQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDUsIAogIGZpZy5wYXRoID0gJ2ZpZ3VyZXMvdGNnYS1iaWNfJywKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwgCiAgc2l6ZSA9ICJ0aW55IiwgCiAgZWNobyA9IFRSVUUsIAogIGV2YWwgPSBUUlVFLCAKICB3YXJuaW5nID0gRkFMU0UsIAogIG1lc3NhZ2UgPSBGQUxTRSwgCiAgcmVzdWx0cyA9IFRSVUUsIAogIGNvbW1lbnQgPSAiIikKCm9wdGlvbnMoc2NpcGVuID0gMTIpICMjIE1heCBudW1iZXIgb2YgZGlnaXRzIGZvciBub24tc2NpZW50aWZpYyBub3RhdGlvbgojIGtuaXRyOjphc2lzX291dHB1dCgiXFxmb290bm90ZXNpemUiKQoKYGBgCgpgYGB7ciBsaWJyYXJpZXMsIGVjaG89RkFMU0UsIGV2YWw9VFJVRX0KCiMgTG9hZCByZXF1aXJlZCBDUkFOIFIgbGlicmFyaWVzCnJlcXVpcmVkTGliIDwtIGMoImtuaXRyIiwgIkZhY3RvTWluZVIiLCAiZmFjdG9leHRyYSIsICJwaGVhdG1hcCIpCmZvciAobGliIGluIHJlcXVpcmVkTGliKSB7CiAgaWYgKCFyZXF1aXJlKGxpYiwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcyhsaWIsICkKICB9CiAgcmVxdWlyZShsaWIsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKfQoKYGBgCgojIyBTeW5vcHNpcyBkdSBwcm9qZXQKCiMjIyBUcmF2YWlsIGRlbWFuZMOpCgpMZSBidXQgZGUgY2UgdHJhdmFpbCBlc3QgZGUgbWV0dHJlIGVuIG9ldXZyZSBsZXMgbcOpdGhvZGVzIHZ1ZXMgZGFucyBsZSBtb2R1bGUgMyAiUiBldCBzdGF0aXN0aXF1ZXMiIHBvdXIgZXhwbG9yZXIgbGUgamV1IGRlIGRvbm7DqWVzIGRlIFBhdm9rb3ZpYywgZXQgZGUgcmVuZHJlIHVuIHJhcHBvcnQgZCdhbmFseXNlIGF1IGZvcm1hdCBSIG1hcmtkb3duLiAKCk5vdXMgZm91cm5pc3NvbnMgY2ktZGVzc291cyB1bmUgdHJhbWUgYXZlYyBsZXMgcHJpbmNpcGFsZXMgc2VjdGlvbnMgYXR0ZW5kdWVzLiBDZXJ0YWluZXMgY29udGllbm5lbnQgZMOpasOgIGR1IGNvZGUuIFZvdXMgZGV2cmV6IGVuIGNvbXBsw6l0ZXIgZCdhdXRyZXMuIFNlbnRlei12b3VzIGxpYnJlcyBkJ2FkYXB0ZXIgY2V0dGUgdHJhbWUgb3UgZCd5IGFqb3V0ZXIgZGVzIGFuYWx5c2VzIGNvbXBsw6ltZW50YWlyZXMgc2kgZWxsZXMgdm91cyBhaWRlbnQgw6AgaW50ZXJwcsOpdGVyIHZvcyByw6lzdWx0YXRzLiAKCiMjIyBSZW1pc2UgZHUgcmFwcG9ydAoKRGF0ZTogKipsZSAxMCBtYWkgMjAyMSBtaW51aXQqKi4gIFNpIHZvdXMgYW50aWNpcGV6IHVuIHByb2Jsw6htZSBwb3VyIHJlbWV0dHJlIGxlIHJhcHBvcnQgw6AgY2V0dGUgZGF0ZSBjb250YWN0ZXotbm91cyBhdXNzaSByYXBpZGVtZW50IHF1ZSBwb3NzaWJsZSBwb3VyIHF1ZSBub3VzIHB1aXNzaW9ucyBwcsOpdm9pciB1bmUgcmVtaXNlIHBsdXMgdGFyZGl2ZS4gCgotIENvbW1lbmNleiBwYXIgcmVub21tZXIgbGUgZmljaGllciBSbWQgZW4gcmVtcGxhw6dhbnQgUHJlbm9tLU5PTSBwYXIgdm9zIG5vbSBldCBwcsOpbm9tLiAKLSBMZSByYXBwb3J0IGVzdCBhdHRlbmR1IGVuIGZvcm1hdHMgUm1kICsgSFRNTCAoZW4gZ2FyZGFudCBsJ29wdGlvbiBzZWxmX2NvbnRhaW5lZCBkZSBsJ2VuLXTDqnRlIGFjdGl2w6llKS4gCi0gRMOpcG9zZXogbGVzIGZpY2hpZXJzIGRhbnMgdW4gc291cy1kb3NzaWVyIGRlIHZvdGUgY29tcHRlIGR1IGNsdXN0ZXIuIEF0dGVudGlvbiwgdmVpbGxleiDDoCByZXNwZWN0ZXIgcHLDqWNpc8OpbWVudCBjZXR0ZSBzdHJ1Y3R1cmUgZGUgY2hlbWluIGNhciBub3VzIG5vdXMgYmFzZXJvbnMgZGVzc3VzIHBvdXIgcsOpY3Vww6lyZXIgdm9zIHLDqXN1bHRhdHMuIAoKICAgIGAvc2hhcmVkL3Byb2plY3RzL2R1YmlpMjAyMS9bbG9naW5dL20zLXN0YXQtUi9taW5pLXByb2pldGAgCgojIyMgQ3JpdMOocmVzIGQnw6l2YWx1YXRpb24KCi0gUmVwcm9kdWN0aWJpbGl0w6kgZGVzIGFuYWx5c2VzOiBub3VzIHRlbnRlcm9ucyBkZSByZWfDqW7DqXJlciBsZSByYXBwb3J0IEhUTUwgw6AgcGFydGlyIGRlIHZvdHJlIFJtZCwgZW4gcGFydGFudCBkZSBub3RyZSBjb21wdGUgc3VyIGxlIHNlcnZldXIgSUZCLiAKLSBNYW5pcHVsYXRpb24gZGVzIG9iamV0cyBSCi0gTW9iaWxpc2F0aW9uIGRlcyBtw6l0aG9kZXMgc3RhdGlzdGlxdWVzIHZ1ZXMgYXUgY291cnMKLSBQZXJ0aW5lbmNlIGRlcyBpbnRlcnByw6l0YXRpb25zIHN0YXRpc3RpcXVlcwotIFBlcnRpbmVuY2UgZGVzIGludGVycHLDqXRhdGlvbnMgYmlvbG9naXF1ZXMKLSBDbGFydMOpIGRlIGxhIHLDqWRhY3Rpb24KLSBDbGFydMOpIGRlcyBpbGx1c3RyYXRpb25zIChmaWd1cmVzIGV0IHRhYmxlYXV4KTogZ3JhcGhpc21lcywgbMOpZ2VuZGVzIC4uLgoKTm91cyB2b3VzIGVuY291cmFnZW9ucyDDoCBhc3N1cmVyIGxhIGxpc2liaWxwaXTDqSBkZSB2b3RyZSBjb2RlIChzeW50YXhlLCBub21tYWdlIGRlcyB2YXJpYWJsZXMsIGNvbW1lbnRhaXJlcyBkZSBjb2RlKQoKIyMjIE9iamVjdGlmcyBzY2llbnRpZmlxdWVzCgpOb3VzIHBhcnRvbnMgZHUgbcOqbWUgamV1IGRlIGRvbm7DqWVzICpGaWwgUm91Z2UqIGRlIGNlIG1vZHVsZSBpc3N1ZXMgZGUgbGEgcHVibGljYXRpb24gUGF2a292aWMsIE0uLCBQYW50YW5vLCBMLiwgR2VybGFjaCwgQy5WLiBldCBhbC4gTXVsdGkgb21pY3MgYW5hbHlzaXMgb2YgZmlicm90aWMga2lkbmV5cyBpbiB0d28gbW91c2UgbW9kZWxzLiBTY2kgRGF0YSA2LCA5MiAoMjAxOSkuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMDM4L3M0MTU5Ny0wMTktMDA5NS01CgoqKlJhcHBlbCBzdXIgbGVzIMOpY2hhbnRpbGxvbnM6KioKCkRldXggbW9kw6hsZXMgZGUgZmlicm9zZSByw6luYWxlIGNoZXogbGEgc291cmlzIHNvbnQgw6l0dWRpw6lzOgoKMS4gTGUgcHJlbWllciBlc3QgdW4gbW9kw6hsZSBkZSBuw6lwaHJvcGF0aGllIHLDqXZlcnNpYmxlIGluZHVpdGUgcGFyIGwnYWNpZGUgZm9saXF1ZSAoZm9saWMgYWNpZCAoRkEpKS4gTGVzIHNvdXJpcyBvbnQgw6l0w6kgc2FjcmlmacOpZXMgYXZhbnQgbGUgdHJhaXRlbWVudCAoZGF5IDApLCBwdWlzIMOgIGpvdXIgMSwgMiwgNyBldCAxNCBkYXlzIGFwcsOocyB1bmUgc2V1bGUgaW5qZWN0aW9uIGQnYWNpZGUgZm9saXF1ZS4KCjIuIExlIHNlY29uZCBlc3QgdW4gbW9kw6hsZSBpcnLDqXZlcnNpYmxlIGluZHVpdCBjaHJpcnVyZ2ljYWxlbWVudCAodW5pbGF0ZXJhbCB1cmV0ZXJhbCBvYnN0cnVjdGlvbiAoVVVPKSkuIGxlcyBzb3VyaXMgb250IMOpdMOpIHNhY3JpZmnDqWVzIGF2YW50IG9ic3RydWN0aW9uIChkYXkgMCkgZXQgw6AgMywgNyBldCAxNCBqb3VycyBhcHLDqHMgb2JzdHJ1Y3Rpb24gcGFyIGxpZ2F0aW9uIGRlIGwndXJldMOocmUgZHUgcmVpbiBnYXVjaGUuCgpBIHBhcnRpciBkZSBjZXMgZXh0cmFpdHMgZGUgcmVpbiwgbCdBUk4gbWVzc2FnZXIgdG90YWwgZXQgbGVzIHBldGl0cyBBUk5zIG9udCDDqXTDqSBzw6lxdWVuY8OpcyBldCBsZXMgcHJvdMOpaW5lcyBjYXJhdMOpcmlzw6llcyBwYXIgc3BlY3Ryb23DqXRyaWUgZGUgbWFzc2UgZW4gdGFuZGVtIChUTVQpLgoKKipCdXQgc2NpZW50aWZpcXVlOioqIERhbnMgbGUgdHV0b3JpZWwgc3VyIGxlcyBkYXRhZnJhbWVzLCB2b3VzIGF2ZXogdHJhdmFpbGzDqSBzdXIgbGVzIGRvbm7DqWVzIGRlICoqKnRyYW5zY3JpcHRvbWUgZHUgbW9kw6hsZSBVVU8qKiouIERhbnMgY2UgbWluaS1wcm9qZXQsIHZvdXMgYWxsZXogdHJhdmFpbGxlciBzdXIgbGVzIGRvbm7DqWVzIGR1IG1vZMOobGUgRkEgYWZpbiBkJ2lkZW50aWZpZXIgZGUgcmVncm91cGVyIGxlcyBvYnNlcnZhdGlvbnMgKMOpY2hhbnRpbGxvbikgZXQgbGVzIGfDqG5lcyBzZWxvbiBkZXMgcHJvZmlscyBkJ2V4cHJlc3Npb24gc2ltaWxhaXJlcy4KCioqVm90cmUgcHJvamV0IHNlIGTDqWNvbXBvc2UgZW4gNCBwYXJ0aWVzOioqCgoxLiBzdGF0c2l0aXF1ZXMgZGVzY3JpcHRpdmVzIGRlcyBkb25uw6llcyBicnV0ZXMKMi4gbm9ybWFsaXNhdGlvbiBkZXMgZG9ubsOpZXMgOiBjb21tYW5kZXMgZm91cm5pZXMKMy4gc3RhdGlzdGlxdWVzIGRlc2NyaXB0aXZlcyBkZXMgZG9ubsOpZXMgbm9ybWFsaXPDqWVzCjQuIGFuYWx5c2UgZGUgcmVncm91cGVtZW50IGRlcyBkb25uw6llcwoKCiMjIDEuIExlcyBkb25uw6llcyBicnV0ZXMKCiMjIyBDaGFyZ2VtZW50IGRlcyBkb25uw6llcyBicnV0ZXMKCioqKlZvdXMgbidhdmV6IHJpZW4gw6AgY29kZXIgaWNpLiBMZSBjb2RlIGVzdCBmb3VybmkuKioqCgpMZSBibG9jIHN1aXZhbnQgY29udGllbnQgdW5lIGZvbmN0aW9uIHF1aSBwZXJtZXQgZGUgdMOpbMOpY2hhcmdlciB1biBmaWNoaWVyIGRhbnMgbCdlc3BhY2UgZGUgdHJhdmFpbCwgc2F1ZiBzJ2lsIGVzdCBkw6lqw6AgcHLDqXNlbnQuIE5vdXMgbCd1dGlsaXNlcm9ucyBlbnN1aXRlIHBvdXIgdMOpbMOpY2hhcmdlciBsZXMgZG9ubsOpZXMgw6AgYW5hbHlzZXIgZW4gw6l2aXRhbnQgZGUgcmVmYWlyZSBsZSB0cmFuc2ZlcnQgw6AgY2hhcXVlIGV4w6ljdXRpb24gZGUgbCdhbmFseXNlLiAKCmBgYHtyIGZ1bmN0aW9uX2Rvd25sb2FkX29ubHlfb25jZX0KIycgQHRpdGxlIERvd25sb2FkIGEgZmlsZSBvbmx5IGlmIGl0IGlzIG5vdCB5ZXQgaGVyZQojJyBAYXV0aG9yIEphY3F1ZXMgdmFuIEhlbGRlbiBlbWFpbHtKYWNxdWVzLnZhbi1IZWxkZW5AQGZyYW5jZS1iaW9pbmZvcm1hdGlxdWUuZnJ9CiMnIEBwYXJhbSB1cmxfYmFzZSBiYXNlIG9mIHRoZSBVUkwsIHRoYXQgd2lsbCBiZSBwcmVwZW5kZWQgdG8gdGhlIGZpbGUgbmFtZQojJyBAcGFyYW0gZmlsZV9uYW1lIG5hbWUgb2YgdGhlIGZpbGUgKHNob3VsZCBub3QgY29udGFpbiBhbnkgcGF0aCkKIycgQHBhcmFtIGxvY2FsX2ZvbGRlciBwYXRoIG9mIGEgbG9jYWwgZm9sZGVyIHdoZXJlIHRoZSBmaWxlIHNob3VsZCBiZSBzdG9yZWQKIycgQHJldHVybiB0aGUgZnVuY3Rpb24gcmV0dXJucyB0aGUgcGF0aCBvZiB0aGUgbG9jYWwgZmlsZSwgYnVpbHQgZnJvbSBsb2NhbF9mb2xkZXIgYW5kIGZpbGVfbmFtZQojJyBAZXhwb3J0wqkKZG93bmxvYWRfb25seV9vbmNlIDwtIGZ1bmN0aW9uKAogIHVybF9iYXNlLCAKICBmaWxlX25hbWUsCiAgbG9jYWxfZm9sZGVyKSB7CgogICMjIERlZmluZSB0aGUgc291cmNlIFVSTCAgCiAgdXJsIDwtIGZpbGUucGF0aCh1cmxfYmFzZSwgZmlsZV9uYW1lKQogIG1lc3NhZ2UoIlNvdXJjZSBVUkxcblx0IiwgIHVybCkKCiAgIyMgRGVmaW5lIHRoZSBsb2NhbCBmaWxlCiAgbG9jYWxfZmlsZSA8LSBmaWxlLnBhdGgobG9jYWxfZm9sZGVyLCBmaWxlX25hbWUpCiAgCiAgIyMgQ3JlYXRlIHRoZSBsb2NhbCBkYXRhIGZvbGRlciBpZiBpdCBkb2VzIG5vdCBleGlzdAogIGRpci5jcmVhdGUobG9jYWxfZm9sZGVyLCBzaG93V2FybmluZ3MgPSBGQUxTRSwgcmVjdXJzaXZlID0gVFJVRSkKICAKICAjIyBEb3dubG9hZCB0aGUgZmlsZSBPTkxZIGlmIGl0IGlzIG5vdCBhbHJlYWR5IHRoZXJlCiAgaWYgKCFmaWxlLmV4aXN0cyhsb2NhbF9maWxlKSkgewogICAgbWVzc2FnZSgiRG93bmxvYWRpbmcgZmlsZSBmcm9tIHNvdXJjZSBVUkwgdG8gbG9jYWwgZmlsZVxuXHQiLCAKICAgICAgICAgICAgbG9jYWxfZmlsZSkKICAgIGRvd25sb2FkLmZpbGUodXJsID0gdXJsLCBkZXN0ZmlsZSA9IGxvY2FsX2ZpbGUpCiAgfSBlbHNlIHsKICAgIG1lc3NhZ2UoIkxvY2FsIGZpbGUgYWxyZWFkeSBleGlzdHMsIG5vIG5lZWQgdG8gZG93bmxvYWRcblx0IiwgCiAgICAgICAgICAgIGxvY2FsX2ZpbGUpCiAgfQogIAogIHJldHVybihsb2NhbF9maWxlKQp9CmBgYAoKTm91cyB0w6lsw6ljaGFyZ2VvbnMgZGV1eCBmaWNoaWVycyBkYW5zIHVuIGRvc3NpZXIgbG9jYWwgYH4vbTMtc3RhdC1SL3BhdmtvdmljX2FuYWx5c2lzYCAqKih2b3VzIHBvdXZleiBjaGFuZ2VyIGxlIG5vbSBvdSBjaGVtaW4gZGFucyBsZSBjaHVuayBjaS1kZXNzb3VzKSoqLCBldCBsZXMgY2hhcmdlb25zIGRhbnMgbGVzIGRhdGEuZnJhbWVzIHN1aXZhbnRzOiAKCi0gRG9ubsOpZXMgYnJ1dGVzIGRlIHRyYW5zY3JpcHRvbWU6IGBmYV9leHByX3Jhd2AKLSBNw6l0YWRvbm7DqWVzOiBgZmFfbWV0YWAKCmBgYHtyIGRvd25sb2FkX2FuZF9sb2FkfQojIyBEZWZpbmUgdGhlIHJlbW90ZSBVUkwgYW5kIGxvY2FsIGZvbGRlcgpwYXZrb3ZpY191cmwgPC0gImh0dHBzOi8vZ2l0aHViLmNvbS9EVS1CaWkvbW9kdWxlLTMtU3RhdC1SL3Jhdy9tYXN0ZXIvc3RhdC1SXzIwMjEvZGF0YS9wYXZrb3ZpY18yMDE5LyIKCiMjIERlZmluZSB0aGUgbG9jYWwgZm9sZGVyIGZvciB0aGlzIGFuYWx5c2lzICh3aGVyZSB0aGUgZGF0YSB3aWxsIGJlIGRvd25sb2FkZWQgYW5kIHRoZSByZXN1bHRzIGdlbmVyYXRlZCkKcGF2a292aWNfZm9sZGVyIDwtICJ+L20zLXN0YXQtUi9wYXZrb3ZpY19hbmFseXNpcyIKCiMjIERlZmluZSBhIHN1Yi1mb2xkZXIgZm9yIHRoZSBkYXRhCnBhdmtvdmljX2RhdGFfZm9sZGVyIDwtIGZpbGUucGF0aChwYXZrb3ZpY19mb2xkZXIsICJkYXRhIikKCiMjIERvd25sb2FkIGFuZCBsb2FkIHRoZSBleHByZXNzaW9uIGRhdGEgdGFibGUKIyMgTm90ZTogd2UgdXNlIGNoZWNrLm5hbWVzPUZBTFNFIHRvIGF2b2lkIHJlcGxhY2luZyBoeXBoZW5zIGJ5IGRvdHMKIyMgaW4gc2FtcGxlIG5hbWVzLCBiZWNhdXNlIHdlIHdhbnQgdG8ga2VlcCB0aGVtIGFzIGluIHRoZSAKIyMgb3JpZ2luYWwgZGF0YSBmaWxlcy4gCm1lc3NhZ2UoIkRvd25sb2FkaW5nIEZBIHRyYW5zY3JpcHRvbWUgZmlsZVx0IiwgImZhX3Jhd19jb3VudHMudHN2Lmd6IiwKICAiXG5cdGZyb21cdCIsIHBhdmtvdmljX3VybCkKZmFfZXhwcl9maWxlIDwtIGRvd25sb2FkX29ubHlfb25jZSgKICB1cmxfYmFzZSA9IHBhdmtvdmljX3VybCwgCiAgZmlsZV9uYW1lID0gImZhX3Jhd19jb3VudHMudHN2Lmd6IiwKICBsb2NhbF9mb2xkZXIgPSBwYXZrb3ZpY19kYXRhX2ZvbGRlcikKCiMjIExvYWQgdGhlIGV4cHJlc2RzaW9uIHRhYmxlCm1lc3NhZ2UoIkxvYWRpbmcgRkEgdHJhbnNjcmlwdG9tZSBkYXRhIGZyb21cblx0IiwgZmFfZXhwcl9maWxlKQpmYV9leHByX3JhdyA8LSByZWFkLmRlbGltKGZpbGUgPSBmYV9leHByX2ZpbGUsIAogICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEpCgojIyBEb3dubG9hZCB0aGUgbWV0YWRhdGEgZmlsZQptZXNzYWdlKCJEb3dubG9hZGluZyBGQSBtZXRhZGF0YSBmaWxlXHQiLCAiZmFfdHJhbnNjcmlwdG9tZV9tZXRhZGF0YS50c3YiLAogICJcblx0ZnJvbVx0IiwgcGF2a292aWNfdXJsKQpmYV9tZXRhX2ZpbGUgPC0gZG93bmxvYWRfb25seV9vbmNlKAogIHVybF9iYXNlID0gcGF2a292aWNfdXJsLCAKICBmaWxlX25hbWUgPSAiZmFfdHJhbnNjcmlwdG9tZV9tZXRhZGF0YS50c3YiLAogIGxvY2FsX2ZvbGRlciA9IHBhdmtvdmljX2RhdGFfZm9sZGVyKQoKIyMgTG9hZCB0aGUgbWV0YWRhdGEKbWVzc2FnZSgiTG9hZGluZyBGQSBtZXRhZGF0YSBmcm9tXG5cdCIsIGZhX21ldGFfZmlsZSkKZmFfbWV0YSA8LSByZWFkLmRlbGltKGZpbGUgPSBmYV9tZXRhX2ZpbGUsIAogICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEpCgpgYGAKCk5vdXMgcmVnYXJkb25zIGxhIHN0cnVjdHVyZSBkZSBjaGFxdWUgZGF0YWZyYW1lLgpgYGB7ciBpbnNlcGN0IGRhdGF9CnN0cihmYV9leHByX3JhdykKc3RyKGZhX21ldGEpCmBgYAoKTGVzIGRldXggZmljaGllcnMgbmUgZG9ubmVudCBwYXMgbGVzIG9ic2VydmF0aW9ucyBkZSBsJ8OpY2hhbnRpbGxvbiBkYW5zIGxlIG3Dqm1lIG9yZHJlOgpgYGB7ciBjaGVjayBkYXRhIG9yZGVyfQpmYV9tZXRhJHNhbXBsZU5hbWUgPT0gbmFtZXMoZmFfZXhwcl9yYXcpCmBgYAoKTm91cyBsZXMgcsOpb3JnYW5pc29ucyBsZXMgw6ljaGFudGlsbG9ucyBkYW5zIGwnb3JkcmUgZGUgbCdleHDDqXJpZW5jZTogY29uZGl0aW9uIG5vcm1hbGUsIHB1aXMgZGF5IDEgw6AgMTQgYXZlYyBsZXMgMyByw6lwbGljYXRzLgpgYGB7ciByZW9kZXIgZGF0YX0Kc2FtcGxlX29yZGVyIDwtIGMocGFzdGUwKCJub3JtYWxfIiwgMTozKSwgcGFzdGUwKCJkYXkxXyIsIDE6MyksIHBhc3RlMCgiZGF5Ml8iLCAxOjMpLCBwYXN0ZTAoImRheTNfIiwgMTozKSwgcGFzdGUwKCJkYXk3XyIsIDE6MyksIHBhc3RlMCgiZGF5MTRfIiwgMTozKSkKCmZhX2V4cHJfcmF3IDwtIGZhX2V4cHJfcmF3WyxzYW1wbGVfb3JkZXJdCmZhX21ldGEgPC0gZmFfbWV0YVttYXRjaChzYW1wbGVfb3JkZXIsIGZhX21ldGEkc2FtcGxlTmFtZSksXQoKIyBWaWV3KGZhX21ldGEpCmthYmxlKGZhX21ldGEsIGNhcHRpb24gPSAiTWV0ZGF0YSBmb3IgUGF2a292b2MgRkEgdHJhbnNjcmlwdG9tZSIpCmBgYAoKPT4gQWluc2ksIG5vdXMgYXZvbnMgdW4gamV1IGRlIGRvbm7DqWVzIGF2ZWMgdW4gw6ljaGFudGlsbG9uIGRlIGByIG5yb3coZmFfbWV0YSlgIG9ic2VydmF0aW9ucyBldCBkZXMgZG9ubsOpZXMgZCdleHByZXNzaW9uIGRlIGByIG5yb3coZmFfZXhwcl9yYXcpYCBnw6huZXMuCgojIyMgVHJhbnNmb3JtYXRpb24gbG9nMgoKKioqQSB2b3VzIGRlIGpvdWVyISoqKgoKQXBwbGlxdWV6IHVuZSB0cmFuc2Zvcm1hdGlvbiBsb2cyIGRlcyBkb25uw6llcyBicnV0ZXMsIGFwcsOocyBhdm9pciBham91dMOpIHVuIGVwc2lsb24gJFxlcHNpbG9uID0gMSQgKGxlcyB2YWxldXJzIG51bGxlcyBzZXJvbnQgZG9uYyByZXByw6lzZW50w6llcyBwYXIgdW4gbG9nMihjb3VudHMpIHZhbGFudCAkMCQuIFN0b2NrZXogbGUgcsOpc3VsdGF0IGRhbnMgdW4gZGF0YS5mcmFtZSBub21tw6kgYGZhX2V4cHJfbG9nMmAuCgpBZmZjaGV6IHVuIGZyYWdtZW50IGRlcyB0YWJsZWF1eCBgZmFfZXhwcl9yYXdgIGV0IGBmYV9leHByX2xvZzJgIGVuIHPDqWxlY3Rpb25uYW50IGxlcyBsaWduZXMgMTAwIMOgIDEwOSBldCBsZXMgY29sb25uZXMgNSDDoCAxMCwgYWZpbiBkZSB2b3VzIGFzc3VyZXIgcXVlIGxhIHRyYW5zZm9ybWF0aW9uIGxvZzIgYSBiaWVuIGZvbmN0aW9ubsOpLiAKCmBgYHtyIGxvZzJfdHJhbnNmb3JtfQojIyBMb2cyIHRyYW5zZm9ybWF0aW9uIG9mIHRoZSB0cmFuc2NyaXB0b21lIGRhdGEKZXBzaWxvbiA8LSAxCmZhX2V4cHJfbG9nMiA8LSBsb2cyKGZhX2V4cHJfcmF3ICsgZXBzaWxvbikKIyBkaW0oZmFfZXhwcl9sb2cyKQojIFZpZXcoaGVhZChmYV9leHByX2xvZzIpKQoKIyMgRGlzcGxheSBvZiBhIGZyYWdtZW50IG9mIHRoZSBkYXRhIGJlZm9yZSBhbmQgYWZ0ZXIgbG9nMiB0cmFuc2Zvcm1hdGlvbgprYWJsZShmYV9leHByX3Jhd1sxMDA6MTA5LCA1OjEwXSwgY2FwdGlvbiA9ICJGcmFnbWVudCBkZXMgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMgYnJ1dGVzIikKa2FibGUoZmFfZXhwcl9sb2cyWzEwMDoxMDksIDU6MTBdLCBjYXB0aW9uID0gIkZyYWdtZW50IGRlcyBkb25uw6llcyB0cmFuc2NyaXB0b21pcXVlcyBhcHLDqHMgdHJhbnNmb3JtYXRpb24gbG9nMiIpCmBgYAoKCiMjIyBTdGF0aXN0aXF1ZXMgZGVzY3JpcHRpdmVzCgoqKipBIHZvdXMgZGUgam91ZXIhKioqCgpEYW5zIGxlIHR1dG9yaWFsIHN1ciBsZXMgZGF0YWZyYW1lcyBzdXIgbGUgamV1IGRlIGRvbm7DqWVzICJ1dW8iIChyZWxpc2V6IGxlIGNvcnJpZ8OpKSwgbm91cyB2b3VzIGF2b25zIGRlbWFuZMOpIGRlIGNyw6llciB1biBkYXRhLmZyYW1lIHF1aSBjb2xsZWN0ZXJhIGxlcyBzdGF0aXN0aXF1ZXMgcGFyIGfDqG5lIGV0IHBhciDDqWNoYW50aWxsb24uIE5vdXMgdm91cyBkZW1hbmRvbnMgZGUgcsOpYWxpc2VyIHVuZSDDqXR1ZGUgc2ltaWxhaXJlIHN1ciBsZXMgZG9ubsOpZXMgIkZBIi4KCgojIyMjIFBhciDDqWNoYW50aWxsb24gYXZhbnQgbm9ybWFsaXNhdGlvbgoKTm91cyBjcsOpb25zIHVuIGRhdGEuZnJhbWUgbm9tbcOpIGBzYW1wbGVfc3RhdF9wcmVub3JtYCBxdWkgY29tcG9ydGVyYSB1bmUgbGlnbmUgcGFyIMOpY2hhbnRpbGxvbiBldCB1bmUgY29sb25uZSBwYXIgc3RhdGlzdGlxdWUsIGV0IGNhbGN1bGV6IGxlcyBzdGF0aXN0aXF1ZXMgc3VpdmFudGVzIHN1ciBsZXMgdmFsZXVycyBsb2cyIGQnZXhwcmVzc2lvbiBkZSBjaGFxdWUgw6ljaGFudGlsbG9uOgoKLSBtb3llbm5lCi0gw6ljYXJ0LXR5cGUKLSBpbnRlcnZhbGxlIGludGVyLXF1YXJ0aWxlcwotIHByZW1pZXIgcXVhcnRpbGUKLSBtw6lkaWFuZQotIHRyb2lzacOobWUgcXVhcnRpbGUKLSBtYXhpbXVtCi0gbm9tYnJlIGRlIHZhbGV1cnMgbnVsbGVzCgpJbCBzZXJhIGFmZmljaMOpIGF2ZWMgbGEgZm9uY3Rpb24gYGthYmxlKClgIChuJ291YmxpZXogcGFzIGxhIGzDqWdlbmRlKS4gCgpgYGB7ciBzYW1wbGVfc3RhdF9wcmVfbm9ybX0KCnNhbXBsZV9zdGF0X3ByZW5vcm0gPC0gZGF0YS5mcmFtZSgKICBtZWFuID0gYXBwbHkoZmFfZXhwcl9sb2cyLCAyLCBtZWFuLCBuYS5ybT1UUlVFKSwKICBzZCA9IGFwcGx5KGZhX2V4cHJfbG9nMiwgMiwgc2QsIG5hLnJtPVRSVUUpLAogIGlxciA9IGFwcGx5KGZhX2V4cHJfbG9nMiwgMiwgSVFSLCBuYS5ybT1UUlVFKSwKICBRMSA9IGFwcGx5KGZhX2V4cHJfbG9nMiwgMiwgcXVhbnRpbGUsIHAgPSAwLjI1LCBuYS5ybT1UUlVFKSwKICBtZWRpYW4gPSBhcHBseShmYV9leHByX2xvZzIsIDIsIG1lZGlhbiwgbmEucm09VFJVRSksCiAgUTMgPSBhcHBseShmYV9leHByX2xvZzIsIDIsIHF1YW50aWxlLCBwID0gMC43NSwgbmEucm09VFJVRSksCiAgbWF4ID0gYXBwbHkoZmFfZXhwcl9sb2cyLCAyLCBtYXgsIG5hLnJtPVRSVUUpLAogIG51bGwgPSBhcHBseShmYV9leHByX2xvZzIgPT0gMCwgMiwgc3VtLCBuYS5ybT1UUlVFKQopCgprYWJsZShzYW1wbGVfc3RhdF9wcmVub3JtLCBjYXB0aW9uID0gIlNhbXBsZS13aXNlIHN0YXRpc3RpY3MgYmVmb3JlIG5vcm1hbGlzYXRpb24uIikKCmBgYAoKCiMjIyMgUGFyIGfDqG5lIGF2YW50IG5vcm1hbGlzYXRpb24KCk5vdXMgY3LDqXNvbnMgY2ktZGVzc291cyB1biBkYXRhLmZyYW1lIG5vbW3DqSBgZ2VuZV9zdGF0X3ByZW5vcm1gIHF1aSBjb21wb3J0ZXJhIHVuZSBsaWduZSBwYXIgZ8OobmUgZXQgdW5lIGNvbG9ubmUgcGFyIHN0YXRpc3RpcXVlLCBldCBjYWxjdWxleiBsZXMgc3RhdGlzdGlxdWVzIHN1aXZhbnRlcyBzdXIgbGVzIHZhbGV1cnMgbG9nMiBkZSBjaGFxdWUgZ8OobmUuCgotIG1veWVubmUKLSBtw6lkaWFuZQotIMOpY2FydC10eXBlCi0gcHJlbWllciBxdWFydGlsZQotIHRyb2lzacOobWUgcXVhcnRpbGUKLSBtYXhpbXVtCi0gbm9tYnJlIGRlIHZhbGV1cnMgbnVsbGVzCi0gaW50ZXJ2YWxsZSBpbnRlci1xdWFydGlsZXMKCkNlcyByw6lzdWx0YXRzIHNlcm9udCBzdG9ja8OpcyBkYW5zIHVuIGRhdGEuZnJhbWUgYXZlYyAxIGxpZ25lIHBhciDDqWNoYW50aWxsb24gZXQgMSBjb2xvbm5lIHBhciBzdGF0aXN0aXF1ZS4gVm91cyBhZmZpY2hlcmV6IGxlcyBsaWduZXMgMTAwIMOgIDEwOSBkZSBjZSB0YWJsZWF1IGRlIHN0YXRpc3RpcXVlcyBhdmVjIGxhIGZvbmN0aW9uIGBrYWJsZSgpYCAobidvdWJsaWV6IHBhcyBsYSBsw6lnZW5kZSkuCgpgYGB7ciBnZW5lX3N0YXRfcHJlX25vcm19CiMjIEdlbmUtd2lzZSBzdGF0aXN0aWNzIGJlZm9yZSBub3JtYWxpc2F0aW9uCmdlbmVfc3RhdF9wcmVub3JtIDwtIGRhdGEuZnJhbWUoCiAgbWVhbiA9IGFwcGx5KGZhX2V4cHJfcmF3LCAxLCBtZWFuLCBuYS5ybT1UUlVFKSwKICBzZCA9IGFwcGx5KGZhX2V4cHJfcmF3LCAxLCBzZCwgbmEucm09VFJVRSksCiAgaXFyID0gYXBwbHkoZmFfZXhwcl9yYXcsIDEsIElRUiwgbmEucm09VFJVRSksCiAgUTEgPSBhcHBseShmYV9leHByX3JhdywgMSwgcXVhbnRpbGUsIHAgPSAwLjI1LCBuYS5ybT1UUlVFKSwKICBtZWRpYW4gPSBhcHBseShmYV9leHByX3JhdywgMSwgbWVkaWFuLCBuYS5ybT1UUlVFKSwKICBRMyA9IGFwcGx5KGZhX2V4cHJfcmF3LCAxLCBxdWFudGlsZSwgcCA9IDAuNzUsIG5hLnJtPVRSVUUpLAogIG1heCA9IGFwcGx5KGZhX2V4cHJfcmF3LCAxLCBtYXgsIG5hLnJtPVRSVUUpLAogIG51bGwgPSBhcHBseShmYV9leHByX3JhdyA9PSAwLCAxLCBzdW0sIG5hLnJtPVRSVUUpCikKCmthYmxlKGdlbmVfc3RhdF9wcmVub3JtWzEwMDoxMDksIF0sIGNhcHRpb24gPSAiR2VuZS13aXNlIHN0YXRpc3RpY3MgYmVmb3JlIG5vcm1hbGlzYXRpb24uIikKCmBgYAoKCiMjIDIuIEZpbHRyYWdlIGV0IG5vcm1hbGlzYXRpb24gZGVzIGRvbm7DqWVzCgpOb3VzIGZvdXJuaXNzb25zIGNpLWRlc3NvdXMgbGUgY29kZSBwb3VyIG5vcm1hbGlzZXIgbGVzIGRvbm7DqWVzLCBlbiBzdGFuZGFyZGlzYW50IGxlIDPDqG1lIHF1YW50aWxlLiAKCkxhIG3DqXRob2RlIGNob2lzaWUgaWNpIGNvbnNpc3RlIMOgIAoKLSDDqWNhcnRlciBsZXMgZ8OobmVzICJub24tZMOpdGVjdMOpcyIsIGMnZXN0IC3DoC1kaXJlIGNldXggYXlhbnQgZGVzIHZhbGV1cnMgbnVsbGVzIGRhbnMgYXUgbW9pbnMgOTAlIGRlcyDDqWNoYW50aWxsb25zOwoKLSDDqWNhcnRlciBsZXMgZ8OobmVzIMOgIHBlaW5lIGV4cHJpbcOpcywgYydlc3Qtw6AtZGlyZSBjZXV4IGF5YW50IHVuZSB2YWxldXIgbW95ZW5uZSA8IDEwIChhcmJpdHJhaXJlbWVudCk7CgotIHN0YW5kYXJkaXNlciBsZXMgw6ljaGFudGlsbG9ucyBzdXIgbGUgM8OobWUgcXVhcnRpbGUgZGVzIHZhbGV1cnMgbm9uLW51bGxlcy4gTm91cyB2b3VzIHJhcHBlbG9ucyBxdWUgZCdhdXRyZXMgbcOpdGhvZGVzIHBsdXMgw6lsYWJvcsOpZXMgb250IMOpdMOpIHZ1ZXMgZGFucyBsZXMgbW9kdWxlcyA0IGV0IDUsIG1haXMgbm91cyBhdm9ucyBjaG9pc2kgaWNpIHVuZSBzb2x1dGlvbiBzaW1wbGUgdG91dCBlbiDDqXRhbnQgcm9idXN0ZS4gCgojIyMgRmlsdHJhZ2UgMSA6IMOpbGltaW5hdGlvbiBkZXMgZ8OobmVzIG5vbiBkw6l0ZWN0w6lzIG91IMOgIHBlaW5lIGV4cHJpbcOpcwoKYGBge3IgZ2VuZV9maWx0ZXJpbmd9CiMjIERhdGEgZmlsdGVyaW5nOiBnZW5lcyBoYXZpbmcgYXQgbGVhc3QgOTAlIG51bGwgdmFsdWVzCnVuZGV0ZWN0ZWRfZ2VuZXMgPC0gZ2VuZV9zdGF0X3ByZW5vcm0kbnVsbCA+PSBuY29sKGZhX2V4cHJfcmF3KSAqIDAuOQpwcmludChwYXN0ZTAoIlVuZGV0ZWN0ZWQgZ2VuZXMgKG51bGwgaW4gPj0gOTAlIHNhbXBsZXMpOiAiLCBzdW0odW5kZXRlY3RlZF9nZW5lcykpKQoKIyMgRGF0YSBmaWx0ZXJpbmc6IGdlbmVzIGhhdmluZyBhIG1lYW4gZXhwcmVzc2lvbiA8IDEwCmJhcmVseV9leHByZXNzZWRfZ2VuZXMgPC0gZ2VuZV9zdGF0X3ByZW5vcm0kbWVhbiA8IDEwCnByaW50KHBhc3RlMCgiQmFyZWx5IGV4cHJlc3NlZCBnZW5lcyAobWVhbiA8IDEwKTogIiwgc3VtKGJhcmVseV9leHByZXNzZWRfZ2VuZXMpKSkKCiMjIEFwcGx5IGZpbHRlcmluZyBvbiBib3RoIGNyaXRlcmlhCmRpc2NhcmRlZF9nZW5lcyA8LSB1bmRldGVjdGVkX2dlbmVzIHwgYmFyZWx5X2V4cHJlc3NlZF9nZW5lcwpwcmludChwYXN0ZTAoIkRpc2NhcmRlZCBnZW5lczogIiwgc3VtKGRpc2NhcmRlZF9nZW5lcykpKQprZXB0X2dlbmVzIDwtICFkaXNjYXJkZWRfZ2VuZXMKcHJpbnQocGFzdGUwKCJLZXB0IGdlbmVzOiAiLCBzdW0oa2VwdF9nZW5lcykpKQoKIyMgR2VuZXMgYWZ0ZXIgZmlsdGVyaW5nCmZhX2V4cHJfbG9nMl9maWx0ZXJlZCA8LSBmYV9leHByX2xvZzJba2VwdF9nZW5lcywgXQoKYGBgCgojIyMgTm9ybWFsaXNhdGlvbiBlbnRyZSDDqWNoYW50aWxsb25zCgoqKipWb3VzIG4nYXZleiByaWVuIMOgIGNvZGVyIGljaS4gTGUgY29kZSBlc3QgZm91cm5pLioqKgoKYGBge3Igc2FtcGxlX3N0YW5kYXJkaXNhdGlvbn0KIyMgR2VuZXJhdGUgYSBkYXRhIGZyYW1lIHdoZXJlIG51bGwgdmFsdWVzIGFyZSByZXBsYWNlZCBieSBOQQpmYV9leHByX25vbnVsbCA8LSBmYV9leHByX2xvZzJfZmlsdGVyZWQKZmFfZXhwcl9ub251bGxbZmFfZXhwcl9sb2cyX2ZpbHRlcmVkIDw9IDBdIDwtIE5BCnN1bShpcy5uYShmYV9leHByX25vbnVsbCkpCgojIyBDb21wdXRlIHRoZSAzcmQgcXVhcnRpbGUgb2Ygbm9uLW51bGwgdmFsdWVzIGZvciBlYWNoIHNhbXBsZQpzYW1wbGVfcTNfbm9udWxsIDwtIGFwcGx5KGZhX2V4cHJfbm9udWxsLCAyLCBxdWFudGlsZSwgcHJvYiA9IDAuNzUsIG5hLnJtID0gVFJVRSkKIyBwcmludChzYW1wbGVfcTNfbm9udWxsKQoKIyMgQ29tcHV0ZSB0aGUgQTMgZm9yIGFsbCB0aGUgdmFsdWVzLCB3aGljaCB3aWxsIHNlcnZlIGFzIHRhcmdldCB2YWx1ZSBmb3IgdGhlIHN0YW5kYXJkaXNlZCBzYW1wbGUgUTMKYWxsX3EzX25vbnVsbCA8LSBxdWFudGlsZSh1bmxpc3QoZmFfZXhwcl9ub251bGwpLCBwcm9iID0gMC43NSwgbmEucm0gPSBUUlVFKQojIHByaW50KGFsbF9xM19ub251bGwpCgojIyBTdGFuZGFyZGlzZSBleHByZXNzaW9uIG9uICB0aGUgdGhpcmQgcXVhcnRpbGUgb2Ygbm9uLW51bGwgdmFsdWVzCiMjIEJld2FyZSA6IGZvciB0aGlzIHN0YW5kYXJkaXphdGlvbiB3ZSBrZWVwIHRoZSBudWxsIHZhbHVlcwojIwojIyBUcmljayA6IEkgdHJhbnNwb3NlIHRoZSB0YWJsZSB0byBhcHBseSB0aGUgcmF0aW8gc2FtcGxlIHBlciBzYW1wbGUsIAojIyBhbmQgdGhlbiB0cmFuc3Bvc2UgdGhlIHJlc3VsdCB0byBnZXQgdGhlIGdlbmVzIGluIHJvd3MgYW5kIHNhbXBsZXMgaW4gY29sdW1ucwpmYV9leHByX2xvZzJfc3RhbmRhcmQgPC0gdCh0KGZhX2V4cHJfbG9nMl9maWx0ZXJlZCkgLyBzYW1wbGVfcTNfbm9udWxsICogYWxsX3EzX25vbnVsbCkKIyBxdWFudGlsZSh1bmxpc3QoZmFfZXhwcl9sb2cyX3N0YW5kYXJkKSwgcHJvYnMgPSAwLjc1LCBuYS5ybSA9IFRSVUUpCgojIyBJIGFsc28gY29tcHV0ZSB0aGUgdmFsdWVzIGZvciB0aGUgIm5vbnVsbCIgdGFibGUgZm9yIAojIyB0aGUgc2FrZSBvZiBjb21wYXJpc29uIGFuZCB0byBjaGVjayB0aGF0IHRoZSB0aGlyZCBxdWFudGlsZXMgb2Ygbm9uLW51bGwgCiMjIHZhbHVlcyBhcmUgd2VsbCBpZGVudGljYWwgYWNyb3NzIHNhbXBsZXMuCmZhX2V4cHJfbG9nMl9zdGFuZGFyZF9ub251bGwgPC0gdCh0KGZhX2V4cHJfbm9udWxsKSAvIHNhbXBsZV9xM19ub251bGwgKiBhbGxfcTNfbm9udWxsKQojIHF1YW50aWxlKHVubGlzdChmYV9leHByX2xvZzJfc3RhbmRhcmRfbm9udWxsKSwgcHJvYnMgPSAwLjc1LCBuYS5ybSA9IFRSVUUpCgojIyBDb21wdXRlIDMgYmVmb3JlIGFuZCBhZnRlciBzdGFuZGFyZGlzYXRpb24sIGluY2x1ZGluZyBvciBub3QgdGhlIG51bGwgdmFsdWVzCnN0YW5kYXJkaXNhdGlvbl9pbXBhY3QgPC0gZGF0YS5mcmFtZSgKICBiZWZvcmVfYWxsID0gYXBwbHkoZmFfZXhwcl9sb2cyX2ZpbHRlcmVkLCAyLCBxdWFudGlsZSwgcHJvYiA9ICAwLjc1LCBuYS5ybSA9IFRSVUUpLAogIGJlZm9yZV9ub251bGwgPSBhcHBseShmYV9leHByX25vbnVsbCwgMiwgcXVhbnRpbGUsIHByb2IgPSAgMC43NSwgbmEucm0gPSBUUlVFKSwKICBhZnRlcl9ub251bCA9IGFwcGx5KGZhX2V4cHJfbG9nMl9zdGFuZGFyZF9ub251bGwsIDIsIHF1YW50aWxlLCBwcm9iID0gIDAuNzUsIG5hLnJtID0gVFJVRSksCiAgYWZ0ZXJfYWxsID0gYXBwbHkoZmFfZXhwcl9sb2cyX3N0YW5kYXJkLCAyLCBxdWFudGlsZSwgcHJvYiA9ICAwLjc1LCBuYS5ybSA9IFRSVUUpCikKCiMjIE5vdGU6IGFmdGVyIHN0YW5kYXJkaXphdGlvbiB0aGUgUTMgb2YgdGhlIGRhdGEgd2Ugd2lsbCB1c2Ugc2hvdyBzb21lIHZhcmlhdGlvbnMgCiMjIGJlY2F1c2Ugd2UgY29tcHV0ZSB0aGVtIGhlcmUgd2l0aCB0aGUgbnVsbCB2YWx1ZXMKa2FibGUoc3RhbmRhcmRpc2F0aW9uX2ltcGFjdCwgY2FwdGlvbiA9ICJJbXBhY3Qgb2Ygc3RhbmRhcmRpemF0aW9uIG9uIHRoZSB0aGlyZCBxdWFudGlsZSAoUTMpIHBlciBzYW1wbGUuIFRoaXJkIHF1YW50aWxlcyBhcmUgY29tcHV0ZWQgYmVmb3JlIGFuZCBhZnRlciBzdGFuZGFyZGlzYXRpb24sIHdpdGggZWl0aGVyIGFsbCB0aGUgdmFsdWVzIG9mIHRoZSBmaWx0ZXJlZCB0YWJsZSwgb3Igb25seSB0aGUgbm9uLW51bGwgdmFsdWVzLiAiKQoKYGBgCgojIyAzLiBMZXMgZG9ubsOpZXMgbm9ybWFsaXPDqWVzCgojIyMgU3RhdGlzdGlxdWVzIHBhciBnw6huZSBhcHLDqHMgbm9ybWFsaXNhdGlvbiwgZXQgYW5ub3RhdGlvbiBkZXMgZ8OobmVzCgpHw6luw6lyZXogdW4gZGF0YS5mcmFtZSBhdmVjIHVuZSBsaWduZSBwYXIgZ8OobmUgw6AgcGFydGlyIGR1IHRhYmxlYXUgZGUgZG9ubsOpZXMgbm9ybWFsaXPDqWVzLCBhdmVjIGxlcyBzdGF0aXN0aXF1ZXMgc3VpdmFudGVzICh1bmUgc3RhdGlzdGlxdWUgcGFyIGNvbG9ubmUpOgoKLSBtb3llbm5lCi0gdmFyaWFuY2UKLSDDqWNhcnQtdHlwZQotIGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbgotIG1pbmltdW0KLSBtw6lkaWFuZQotIG1heGltdW0KCkNoYXF1ZSBnw6huZSDDqXRhbnQgZG9ubsOpIHBhciBzb24gaWRlbnRpZmlhbnQgZGFucyBsYSBiYXNlIGRlIGRvbm7DqWVzIEVOU0VNQkwgdm91cyB1dGlsaXNlcmV6IGxlIHBhcXVldCBiaW9tYVJ0IHBvdXIgYWpvdXRlciBkZXMgYW5ub3RhdGlvbnMgw6AgY2hhY3VuIGRlcyBnw6huZXM6IHN5bWJvbGUsIGNocm9tb3NvbWUsIGNvb3Jkb25uw6llcyBnw6lub21pcXVlcy4KCkFqb3V0ZXogdW5lIGNvbG9ubmUgYXZlYyBsZXMgYW5ub3RhdGlvbnMgYmlvbWFydCBkZSBjaGFxdWUgZ8OobmUuIAoKYGBge3IgZ2VuZV9hbm5vdGF0aW9uc30KIyMjIyBHZW5lIGFubm90YXRpb25zICMjIyMKCmBgYAoKCkFmZmljaGV6IGxlcyBsaWduZXMgMTAwIMOgIDEwOSBkZSBjZSB0YWJsZWF1IGRlIHN0YXRpc3RpcXVlcy4gCgoKYGBge3IgZ2VuZV9zdGF0X3Bvc3Rfbm9ybX0KIyMjIyBTdGF0IHBlciBnZW5lIGFmdGVyIG5vcm1hbGlzYXRpb24gIyMjIwoKIyMgR2VuZS13aXNlIHN0YXRpc3RpY3MgYmVmb3JlIG5vcm1hbGlzYXRpb24KZ2VuZV9zdGF0X25vcm0gPC0gZGF0YS5mcmFtZSgKICBtZWFuID0gYXBwbHkoZmFfZXhwcl9sb2cyX3N0YW5kYXJkLCAxLCBtZWFuKSwKICB2YXIgPSBhcHBseShmYV9leHByX2xvZzJfc3RhbmRhcmQsIDEsIHZhciksCiAgc2QgPSBhcHBseShmYV9leHByX2xvZzJfc3RhbmRhcmQsIDEsIHNkKSkKZ2VuZV9zdGF0X25vcm0kViA9IGdlbmVfc3RhdF9ub3JtJHNkIC8gZ2VuZV9zdGF0X25vcm0kbWVhbgpnZW5lX3N0YXRfbm9ybSRtaW4gPSBhcHBseShmYV9leHByX2xvZzJfc3RhbmRhcmQsIDEsIG1pbikKZ2VuZV9zdGF0X25vcm0kbWVkaWFuID0gYXBwbHkoZmFfZXhwcl9sb2cyX3N0YW5kYXJkLCAxLCBtZWRpYW4pCmdlbmVfc3RhdF9ub3JtJG1heCA9IGFwcGx5KGZhX2V4cHJfbG9nMl9zdGFuZGFyZCwgMSwgbWF4KQoKa2FibGUoZ2VuZV9zdGF0X25vcm1bMTAwOjEwOSwgXSwgY2FwdGlvbiA9ICJHZW5lLXdpc2Ugc3RhdGlzdGljcyBhZnRlciBub3JtYWxpc2F0aW9uLiIpCgoKYGBgCgoKIyMjIERpc3RyaWJ1dGlvbiBkZXMgZG9ubsOpZXMKCi0gRGVzc2luZXogc291cyBmb3JtZSBkJ3VuIGhpc3RvZ3JhbW1lIGxhIGRpc3RyaWJ1dGlvbiBkZXMgdmFsZXVycyBhcHLDqHMgbm9ybWFsaXNhdGlvbiAodG91cyDDqWNoYW50aWxsb25zIGNvbmZvbmR1cykKCmBgYHtyIGZhX2V4cHJfbm9ybV9kaXN0cmliLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIHZhbHVlcyAobG9nMiBjb3VudHMpIGFmdGVyIGdlbmUgZmlsdGVyaW5nIGFuZCBzdGFuZGFyZGlzYXRpb24gb24gdGhlIHNhbXBsZS13aXNlIHRoaXJkLXF1YXJ0aWxlIG9mIG5vbi1udWxsIHZhbHVlcy4gVGhlIHZlcnRpY2FsIGxpbmUgaGlnaGxpZ2h0cyB0aGUgbWVhbiB2YWx1ZS4gIn0KaGlzdCh1bmxpc3QoZmFfZXhwcl9sb2cyX3N0YW5kYXJkKSwgCiAgICAgYnJlYWtzID0gc2VxKGZyb20gPSAwLCB0byA9IG1heChmYV9leHByX2xvZzJfc3RhbmRhcmQpICsgMSwgYnkgPSAwLjI1KSwKICAgICB4bGFiID0gImxvZzIoY291bnRzKSBhZnRlciBzdGFuZGFyZGlzYXRpb24iLCAKICAgICB5bGFiID0gIm51bWJlciBvZiBnZW5lcyBhZnRlciBmaWx0ZXJpbmciLAogICAgIGNvbCA9ICIjQkJEREZGIiwKICAgICBsYXMgPSAxLCBjZXguYXhpcyA9IDAuOCwKICAgICBtYWluID0gImRpc3RyaWJ1dGlvbiBhZnRlciBzdGFuZGFyZGlzYXRpb24iKQphYmxpbmUodiA9IG1lYW4oZmFfZXhwcl9sb2cyX3N0YW5kYXJkKSwgY29sID0gImRhcmtncmVlbiIsIGx3ZCA9IDIpCgpgYGAKCgotIERlc3NpbmV6IHVuIGJveCBwbG90IGRlcyDDqWNoYW50aWxsb25zIGF2YW50IGV0IGFwcsOocyBub3JtYWxpc2F0aW9uLCBldCBjb21tZW50ZXogbGEgZmHDp29uIGRvbnQgbCdlZmZldCBkZSBsYSBub3JtYWxpc2F0aW9uIGFwcGFyYcOudCBzdXIgY2VzIGdyYXBoaXF1ZXMuIAoKYGBge3IgYm94cGxvdHNfc3RhbmRhcmRpc2F0aW9uX2ltcGFjdCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEyLCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwPSJCb3ggcGxvdHMgc2hvd2luZyB0aGUgaW1wYWN0IG9mIG5vcm1hbGlzYXRpb24ifQojIyMjIEJveCBwbG90cyB0byBzaG93IG5vcm1hbGlzYXRpb24gaW1wYWN0ICMjIyMKcGFyKG1hciA9IGMoNCw2LDQsMSkpICMjIFNldCB0aGUgbWFyZ2lucwpwYXIobWZyb3cgPSBjKDIsMikpCmJveHBsb3QoZmFfZXhwcl9sb2cyX2ZpbHRlcmVkLCAKICAgICAgICBob3Jpem9udGFsID0gVFJVRSwKICAgICAgICB4bGFiID0gImxvZzIoY291bnRzKSIsIAogICAgICAgIGxhcyA9IDEsIAogICAgICAgIGNvbCA9IGZhX21ldGEkY29sb3IsIAogICAgICAgIG1haW4gPSAiQmVmb3JlIHN0YW5kYXJkaXNhdGlvblxuYWxsIHZhbHVlcyIpCmJveHBsb3QoZmFfZXhwcl9ub251bGwsIAogICAgICAgIGhvcml6b250YWwgPSBUUlVFLCAKICAgICAgICB4bGFiID0gImxvZzIoY291bnRzKSIsIAogICAgICAgIGxhcyA9IDEsIAogICAgICAgIGNvbCA9IGZhX21ldGEkY29sb3IsIAogICAgICAgIG1haW4gPSAiQmVmb3JlIHN0YW5kYXJkaXNhdGlvblxuemVyb3MgZGlzY2FyZGVkIikKYm94cGxvdChmYV9leHByX2xvZzJfc3RhbmRhcmRfbm9udWxsLCAKICAgICAgICBob3Jpem9udGFsID0gVFJVRSwgCiAgICAgICAgeGxhYiA9ICJsb2cyKGNvdW50cykiLCAKICAgICAgICBsYXMgPSAxLCAKICAgICAgICBjb2wgPSBmYV9tZXRhJGNvbG9yLCAKICAgICAgICBtYWluID0gIlN0YW5kYXJkaXNlZFxuemVyb3MgZGlzY2FyZGVkIikKYm94cGxvdChmYV9leHByX2xvZzJfc3RhbmRhcmQsIAogICAgICAgIHhsYWIgPSAibG9nMihjb3VudHMpIiwgCiAgICAgICAgbGFzID0gMSwgCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsIAogICAgICAgIGNvbCA9IGZhX21ldGEkY29sb3IsIAogICAgICAgIG1haW4gPSAiU3RhbmRhcmRpc2VkXG5hbGwgdmFsdWVzIikKcGFyKG1mcm93ID0gYygxLCAxKSkKcGFyKG1hciA9IGMoNCw1LDUsMSkpCgpgYGAKCiMjIDQuIEFuYWx5c2UgZGUgcmVncm91cGVtZW50IGRlcyBkb25uw6llcwoKIyMjIEZpbHRyYWdlIDIgOiBzw6lsZWN0aW9uIGRlIGfDqG5lcyBkJ2V4cHJlc3Npb24gw6lsZXbDqWUgZXQgdmFyaWFibGUKClBvdXIgcsOpZHVpcmUgbGUgbm9tYnJlIGRlIGfDqG5lcywgbm91cyBhbGxvbnMgw6ljYXJ0ZXIgbGVzIGfDqG5lcyBmYWlibGVtZW50IGV4cHJpbcOpcyAobG9nMiBtb3llbiBpbmbDqXJpZXVyIMOgIDQpLCBldCBuZSByZXRlbmlyIHF1ZSBjZXV4IHF1aSBtb250cmVudCBkZXMgdmFyaWF0aW9ucyBpbXBvcnRhbnRlcyBlbnRyZSDDqWNoYW50aWxsb25zLiBQb3VyIGNlIGRlcm5pZXIgY3JpdMOocmUgLCBub3VzIG5vdXMgYmFzb25zIHN1ciBsZSBjb2VmZmljaWVudCBkZSB2YXJpYXRpb24sIGFmaW4gZGUgcmVsYXRpdmlzZXIgbGEgZGlzcGVyc2lvbiAow6ljYXJ0IHR5cGUpIHBhciByYXBwb3J0IMOgIGxhIHRlbmRhbmNlIGNlbnRyYWxlIChtb3llbm5lKS4gCgpTw6lsZWN0aW9ubmV6IGxlcyBnw6huZXMgYXlhbnQgdW4gbml2ZWF1IGxvZzIgbW95ZW4gbWluaW1hbCBzdXDDqXJpZXVyIMOgIDMgKCRzID4gMyQpIGV0IHVuIGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbiBzdXDDqXJpZXVyIMOgIDAuNSAoJFYgPiAwLjUkKS4gTm90ZTogY2VzIHZhbGV1cnMgc29udCBwYXJmYWl0ZW1lbnQgYXJiaXRyYWlyZXMsIGVsbGVzIG9udCDDqXJ0w6kgY2hvaXNpZXMgcG91ciBvYnRlbmlyIHVuIG5vbWJyZSByYWlzb25uYWJsZSBkZSBnw6huZXMuIAoKCmBgYHtyIGdlbmVfc2VsZWN0aW9ufQojIyMjIEdlbmUgc2VsZWN0aW9uICMjIyMKCiMjIENvbXB1dGUgYSBCb29sZWFuIHZlY3RvciBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCBnZW5lIHBhc3NlcyBvciBub3QgdGhlIGV4cHJlc3Npb24gbGV2ZWwgdGhyZXNob2xkCmhpZ2hfZXhwcmVzc2lvbiA8LSBnZW5lX3N0YXRfbm9ybSRtZWFuID4gMwojIHRhYmxlKGhpZ2hfZXhwcmVzc2lvbikgIyBjb3VudCBudW1iZXIgb2YgZ2VuZXMgd2l0aCBoaWdoL3dlYWsgZXhwcmVzc2lvbgoKIyMgQ29tcHV0ZSBhIEJvb2xlYW4gdmVjdG9yIGluZGljYXRpbmcgd2hldGhlciBlYWNoIGdlbmUgcGFzc2VzIG9yIG5vdCB0aGUgdmFyaWF0aW9uIGNvZWZmaWNpZW50IHRocmVzaG9sZApoaWdoX3ZhcmlhdGlvbiA8LSBnZW5lX3N0YXRfbm9ybSRWID4gMC41CiMgdGFibGUoaGlnaF92YXJpYXRpb24pICMgY291bnQgbnVtYmVyIG9mIGdlbmVzIHdpdGggd2VhayBoaWdoIGNvZWZmZmljaWVudCBvZiB2YXJpYXRpb24KCiMjIENvbXB1dGUgYSBCb29sZWFuIHZlY3RvciBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCBnZW5lIHBhc3NlcyBvciBub3QgdGhlIHZhcmlhbmNlIHRocmVzaG9sZAojIGhpZ2hfdmFyaWFuY2UgPC0gZ2VuZV9zdGF0X25vcm0kdmFyID4gMgojIHRhYmxlKGhpZ2hfdmFyaWFuY2UpICMgY291bnQgbnVtYmVyIG9mIGdlbmVzIHdpdGggd2VhayBoaWdoIHZhcmlhbmNlCgojIyBTZWxlY3QgZ2VuZXMgaGF2aW5nIGJvdGggYSBoaWdoIG1lYW4gZXhwcmVzc2lvbiBhbmQgYSBoaWdoIHZhcmlhdGlvbiBjb2VmZmljaWVuCnNlbGVjdGVkX2dlbmVzIDwtIGhpZ2hfdmFyaWF0aW9uICYgaGlnaF9leHByZXNzaW9uCiMgdGFibGUoc2VsZWN0ZWRfZ2VuZXMpICMgY291bnQgbnVtYmVyIG9mIGdlbmVzIHdpdGggd2VhayBoaWdoIGNvZWZmZmljaWVudCBvZiB2YXJpYXRpb24KcHJpbnQocGFzdGUwKCJTZWxlY3RlZCBnZW5lczogIiwgc3VtKHNlbGVjdGVkX2dlbmVzKSkpCgojIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIGV4cHJlc3Npb24gb2YgdGhlIHNlbGVjdGVkIGdlbmVzCmZhX2V4cHJfc2VsZWN0ZWQgPC0gZmFfZXhwcl9sb2cyX3N0YW5kYXJkW3NlbGVjdGVkX2dlbmVzLCBdCgpgYGAKCkRlc3NpbmV6IGRlcyBoaXN0b2dyYW1tZXMgZGVzIHZhbGV1cnMgZCdleHByZXNzaW9uIGF2YW50IGV0IGFwcsOocyBjZXR0ZSBzw6lsZWN0aW9uIGRlIGfDqG5lcywgZXQgY29tbWVudGV6IGxlcyBkaWZmw6lyZW5jZXMuIAoKYGBge3IgaGlzdF9leHByX3NlbGVjdGVkX2dlbmVzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD04LCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwPSJEaXN0cmlidXRpb24gb2YgZXhwcmVzc2lvbiB2YWx1ZXMgYmVmb3JlIGFuZCBhZnRlciBnZW5lIHNlbGVjdGlvbiJ9CiMjIyMgSGlzdG9ncmFtcyBvZiBleHByZXNzaW9uIGJlZm9yZSBhbmQgYWZ0ZXIgZ2VuZSBzZWxlY3Rpb24gIyMjIwoKcGFyKG1mcm93ID0gYygyLDEpKQpoaXN0KHVubGlzdChmYV9leHByX2xvZzJfc3RhbmRhcmQpLCAKICAgICBicmVha3MgPSBzZXEoZnJvbSA9IDAsIHRvID0gbWF4KGZhX2V4cHJfbG9nMl9zdGFuZGFyZCkgKyAxLCBieSA9IDAuMjUpLAogICAgIG1haW4gPSAiU3RhbmRhcmRpemVkIHZhbHVlcyBiZWZvcmUgZ2VuZSBzZWxlY2V0aW9uIiwKICAgICBjb2wgPSAgIiNEREJCRkYiKQoKaGlzdCh1bmxpc3QoZmFfZXhwcl9zZWxlY3RlZCksIAogICAgIGJyZWFrcyA9IHNlcShmcm9tID0gMCwgdG8gPSBtYXgoZmFfZXhwcl9sb2cyX3N0YW5kYXJkKSArIDEsIGJ5ID0gMC4yNSksCiAgICAgbWFpbiA9ICJTdGFuZGFyZGl6ZWQgdmFsdWVzIGFmdGVyIGdlbmUgc2VsZWNldGlvbiIsCiAgICAgY29sID0gICIjRkZEREJCIikKCnBhcihtZnJvdyA9IGMoMSwxKSkKCgojICMjIFNvbWUgcXVpY2sgY2hlY2tzOiB0aGUgc2VsZWN0aW9uIG9mIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyBzZWxlY3QgdGhvc2UgaGF2aW5nIG1hbnkgemVyb3MgLSBhbmQgaGlnaCB2YWx1ZXMgaW4gb3RoZXIgc2FtcGxlcwojIGhpc3QodW5saXN0KGZhX2V4cHJfbG9nMl9maWx0ZXJlZFtoaWdoX2V4cHJlc3Npb24sIF0pLCBicmVha3M9MTAwKQojIGhpc3QodW5saXN0KGZhX2V4cHJfbG9nMl9maWx0ZXJlZFtoaWdoX3ZhcmlhdGlvbiwgXSksIGJyZWFrcz0xMDApCiMgaGlzdCh1bmxpc3QoZmFfZXhwcl9sb2cyX2ZpbHRlcmVkWyFoaWdoX3ZhcmlhdGlvbiwgXSksIGJyZWFrcz0xMDApCiMgaGlzdCh1bmxpc3QoZmFfZXhwcl9sb2cyX2ZpbHRlcmVkW3NlbGVjdGVkX2dlbmVzLCBdKSwgYnJlYWtzPTEwMCkKCmBgYAoKRGVzc2luemVzIHVuIGJveCBwbG90IGRlcyB2YWxldXJzIGQnZXhwcmVzc2lvbiBhdmFudCBldCBhcHLDqHMgc8OpbGVjdGlvbiBkZXMgZ8OobmVzLCBldCBjb21tZW50ZXogbGUgcsOpc3VsdGF0LiAKCmBgYHtyIGJveHBsb3RzX2V4cHJfc2VsZWN0ZWRfZ2VuZXMsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IkJveCBwbG90cyBvZiBzdGFuZGFyZGlzZWQgZXhwcmVzc2lvbiB2YWx1ZXMgYmVmb3JlIGFuZCBhZnRlciBnZW5lIHNlbGVjdGlvbi4gIn0KIyMjIyBIaXN0b2dyYW0gb2YgZXhwcmVzc2lvbiBhZnRlciBnZW5lIHNlbGVjdGlvbiAjIyMjCgpwYXIobWZyb3cgPSBjKDEsMikpCgpib3hwbG90KGZhX2V4cHJfbG9nMl9zdGFuZGFyZCwgCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsCiAgICAgICAgeGxhYiA9ICJsb2cyKGNvdW50cykiLCAKICAgICAgICBsYXMgPSAxLCAKICAgICAgICBjb2wgPSBmYV9tZXRhJGNvbG9yLCAKICAgICAgICBtYWluID0gIkJlZm9yZSBnZW5lIHNlbGVjdGlvblxuc3RhbmRhcmRpc2VkIHZhbHVlcyIpCmJveHBsb3QoZmFfZXhwcl9zZWxlY3RlZCwgCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsCiAgICAgICAgeGxhYiA9ICJsb2cyKGNvdW50cykiLCAKICAgICAgICBsYXMgPSAxLCAKICAgICAgICBjb2wgPSBmYV9tZXRhJGNvbG9yLCAKICAgICAgICBtYWluID0gIkFmdGVyIGdlbmUgc2VsZWN0aW9uXG5zdGFuZGFyZGlzZWQgdmFsdWVzIikKCgpwYXIobWZyb3cgPSBjKDEsMSkpCgoKCmBgYAoKCiMjIyBBQ1AKCkRlc3NpbmV6IHVuIHBsb3QgQUNQIGRlcyDDqWNoYW50aWxsb25zIGVuIGxlcyBjb2xvcmFudCBwYXIgY29uZGl0aW9uLiAKCi0gYXZlYyBsYSBtYXRyaWNlIGQnZXhwcmVzc2lvbiBpbml0aWFsZSAoJGZhX2V4cHIkKQoKYGBge3IgYWNwX3JhdywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OCwgb3V0LndpZHRoPSI2MCUiLCBmaWcuY2FwPSJQQyBwbG90IG9mIHRoZSBzYW1wbGVzIGZyb20gdGhlIHJhdyBleHByZXNzaW9uIHZhbHVlcy4gIn0KIyB0b3VzIGxlcyBnw6huZXMKbWFfcGNhX3Jhd190dCA8LSBQQ0EodChmYV9leHByX3JhdyksIAogICAgICAgICAgICAgICAgICAgICAgc2NhbGUudW5pdCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIGdyYXBoID0gRkFMU0UpCnBsb3QobWFfcGNhX3Jhd190dCwgY2hvaXggPSAiaW5kIikKZnZpel9wY2FfaW5kKG1hX3BjYV9yYXdfdHQsIGNvbC5pbmQgPSBmYV9tZXRhWywgImNvbG9yIl0pCiMgc2V1bGVtZW50IGxlcyBnw6huZXMgc8OpbGVjdGlvbm7DqXMKbWFfcGNhX3Jhd19zZWwgPC0gUENBKHQoZmFfZXhwcl9yYXdbc2VsZWN0ZWRfZ2VuZXMsXSksIAogICAgICAgICAgICAgICAgICAgICAgc2NhbGUudW5pdCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIGdyYXBoID0gRkFMU0UpCmZ2aXpfcGNhX2luZChtYV9wY2FfcmF3X3NlbCwgY29sLmluZCA9IGZhX21ldGFbLCAiY29sb3IiXSkKYGBgCgotIGF2ZWMgbGEgbWF0cmljZSBmaW5hbGUgKHRyYW5zZm9ybWF0aW9uIGxvZzIsIGZpbHRyZSBkZXMgZ8OobmVzIG5vbi1kw6l0ZWN0w6lzLCBzdGFuZGFyZGlzYXRpb24gZXQgc8OpbGVjdGlvbiBkZXMgZ8OobmVzIGZvcnRlbWVudCBleHByaW3DqXMgZXQgYS3DoCBoYXV0IGNvZWZmaWNpZW50IGRlIHZhcmlhdGlvbikKCmBgYHtyIGFjcF9zZWwsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTgsIG91dC53aWR0aD0iNjAlIiwgZmlnLmNhcD0iUEMgcGxvdCBvZiB0aGUgc2FtcGxlcyBmcm9tIHN0YW5kYXJkaXNlZCB2YWx1ZXMgYWZ0ZXIgZ2VuZSBzZWxlY3Rpb24uICJ9Cm1hX3BjYV9zZWwgPC0gUENBKHQoZmFfZXhwcl9zZWxlY3RlZCksIHNjYWxlLnVuaXQgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgZ3JhcGggPSBGQUxTRSkKcGxvdChtYV9wY2Ffc2VsLCBjaG9peCA9ICJ2YXIiKQpwbG90KG1hX3BjYV9zZWwsIGNob2l4ID0gImluZCIpCmZ2aXpfcGNhX2luZChtYV9wY2Ffc2VsLCBjb2wuaW5kID0gZmFfbWV0YVssICJjb2xvciJdKQpgYGAKCiMjIyBDbHVzdGVyaW5nCgotIENhbGN1bGV6IGxlcyBtYXRyaWNlcyBkZSBkaXN0YW5jZSBlbnRyZSDDqWNoYW50aWxsb25zLCBlbiB1dGlsaXNhbnQgcmVzcGVjdGl2ZW1lbnQgbGVzIGRpc3RhbmNlcyBldWNsaWRpZW5uZSAoYGRpc3QoKWApLCBjb2VmZmljaWVudCBkZSBQZWFyc29uIChgY29yKCwgbWV0aG9kID0gInBlYXJzb24iKWApICBldCBkZSBTcGVhcm1hbiAoYGNvcigsIG1ldGhvZCA9ICJzcGVhcm1hbiIpYCkuCgoKYGBge3Igc2FtcGxlX2Rpc3RhbmNlc30KZGlzdF9ldWNfc2VsIDwtIGRpc3QodChmYV9leHByX3NlbGVjdGVkKSkKY29yX3BlYXJzb25fc2VsIDwtIGFzLmRpc3QoMSAtIGNvcihmYV9leHByX3NlbGVjdGVkKSkKY29yX3NwZWFybWFuX3NlbCA8LSAxIC0gYXMuZGlzdChjb3IoZmFfZXhwcl9zZWxlY3RlZCwgbWV0aG9kID0gInNwZWFybWFuIikpCmBgYAoKCgoKLSBFZmZlY3R1ZXogdW4gY2x1c3RlcmluZyBoacOpcmFyY2hpcXVlIGRlcyDDqWNoYW50aWxsb25zLCBlbiB1dGlsaXNhbnQgbGUgY3JpdMOocmUgZGUgV2FyZCAoYHdhcmQuZDJgKSBwb3VyIGwnYWdnbG9tw6lyYXRpb24uIENvbXBhcmV6IGxlcyBhcmJyZXMgZCfDqWNoYW50aWxsb25zIG9idGVudXMgYXZlYyBjZXMgdHJvaXMgbcOpdHJpcXVlcyBldCBjaG9pc2lzc2V6IGNlbGxlIHF1aSB2b3VzIHBhcmHDrnQgbGEgcGx1cyBwZXJ0aW5lbnRlLgoKCgpgYGB7ciBzYW1wbGVfY2x1c3RlcmluZ30KcGFyKG1mcm93ID0gYygxLDMpKQpwbG90KGhjbHVzdChkaXN0X2V1Y19zZWwpLCBoYW5nID0gLTEsCiAgICAgbWFpbiA9ICJldWNsaWRlYW4gZGlzdGFuY2UiKQpwbG90KGhjbHVzdChjb3JfcGVhcnNvbl9zZWwpLCBoYW5nID0gLTEsCiAgICAgbWFpbiA9ICJwZWFyc29uIikKcGxvdChoY2x1c3QoY29yX3NwZWFybWFuX3NlbCksIGhhbmcgPSAtMSwKICAgICBtYWluID0gInNwZWFybWFuIikKcGFyKG1mcm93ID0gYygxLDEpKQpgYGAKCgotIEVmZmVjdHVleiB1biBjbHVzdGVyaW5nIGhpw6lyYXJjaGlxdWUgZGVzIGfDqG5lcyBlbiB1dGlsaXNhbnQgbGEgZGlzdGFuY2UgYmFzw6llIHN1ciBsZSBjb2VmZmljaWVudCBkZSBQZWFyc29uIGV0IGxlIGNyaXTDqHJlIGRlIFdhcmQKCmBgYHtyIGdlbmVfY2x1c3RlcmluZ30KCmNvcl9wZWFyc29uX2dlbmUgPC0gYXMuZGlzdCgxIC0gY29yKHQoZmFfZXhwcl9zZWxlY3RlZCkpKQpwbG90KGhjbHVzdChjb3JfcGVhcnNvbl9nZW5lKSwgaGFuZyA9IC0xLAogICAgIG1haW4gPSAiZ8OobmVzIikKYGBgCgoKLSBEZXNzaW5leiB1biBhcmJyZSBhdmVjIGxlIHLDqXN1bHRhdCBkdSBjbHVzdGVyaW5nIGRlcyBnw6huZXMgZXQgY29tbWVudGV6IHNhIHN0cmN1dHVyZS4gU2kgdm91cyBkZXZpZXogY2hvaXNpciBkZSBmYcOnb24gYXJiaXRyYWlyZSB1biBub21icmUgZGUgY2x1c3RlcnMsIHF1ZSBjaG9pc2lyaWV6LXZvdXMgPyBQb3VycXVvaSA/IFBhcyBkZSBwYW5pcXVlLCBub3VzIHBvdXZvbnMgYXNzdW1lciBpY2kgcXVlIGxhIHLDqXBvbnNlIGNvbXBvcnRlIHVuZSBwYXJ0IGRlIHN1YmplY3Rpdml0w6kuIAoKYGBge3IgZ2VuZV90cmVlfQpwbG90KGhjbHVzdChjb3JfcGVhcnNvbl9nZW5lKSwgaGFuZyA9IC0xLAogICAgIG1haW4gPSAiZ8OobmVzIikKcmVjdC5oY2x1c3QoaGNsdXN0KGNvcl9wZWFyc29uX2dlbmUpLCBrID0gNykKYGBgCgoKLSBEZXNzaW5leiB1bmUgaGVhdG1hcCBkdSByw6lzdWx0YXQsIGVuIHPDqWxlY3Rpb25uYW50IGxlcyBkZXV4IHLDqXN1bHRhdHMgZGUgY2x1c3RlcmluZyBjaS1kZXNzdXMgcG91ciBsZXMgZ8OobmVzIGV0IGxlcyDDqWNoYW50aWxsb25zLiAKCmBgYHtyIGJpY2x1c3RlcmluZ30KcGhlYXRtYXAodChmYV9leHByX3NlbGVjdGVkKSwKICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIikKYGBgCgoKSW50ZXJwcsOpdGV6IGxlcyByw6lzdWx0YXRzIGVuIHF1ZWxxdWVzIHBocmFzZXMuIAoKCiMjIyBFbnJpY2hpc3NlbWVudCBmb25jdGlvbm5lbAoKRWZmZWN0dWV6IHVuZSBhbmFseXNlIGQnZW5yaWNoaXNzZW1lbnQgZm9uY3Rpb25uZWwgYXZlYyBsZXMgcHJpbmNpcGF1eCBjbHVzdGVycyBvYnRlbnVzIGRhbnMgbGEgc2VjdGlvbiBwcsOpY8OpZGVudGUuIAoKYGBge3IgZnVuY3Rpb25hbF9lbnJpY2htZW50fQoKYGBgCgojIyBDb25jbHVzaW9ucyBnw6luw6lyYWxlcwoKClLDqXN1bWV6IGVuIHF1ZWxxdWVzIHBocmFzZXMgdm9zIGNvbmNsdXNpb25zIMOgIHBhcnRpciBkZXMgcsOpc3VsdGF0cyBvYnRlbnVzLiAKCgoK